/** * Casts the rhs to an {@link AssignableValue} using that interface's standard assignment * method. i.e. * * <pre> * [AssignableValueType] lhs; * lhs.[assignMethod](rhs); * </pre> * * or perhaps a type-specific cast: * * <pre> * [AssignableValueType] lhs; * lhs.[castMethod](rhs, lhs.getPrecision()); * </pre> * * <p>Code is also generated to pad and truncate values which need special handling, such as * date and time types. Plus good old null handling. */ private Expression castToAssignableValue() { ensureLhs(); if (requiresSpecializedCast() && rhsType.isNullable()) { assert (lhsType.isNullable()); // propagate null value; normally, we can rely on // assignFrom to do it for us, but for specialized casts, // we can't Expression nullTest = new MethodCall(rhsExp, NullableValue.NULL_IND_ACCESSOR_NAME, new ExpressionList()); addStatement( new ExpressionStatement( new MethodCall( lhsExp, NullableValue.NULL_IND_MUTATOR_NAME, new ExpressionList(nullTest)))); StatementList ifStmtList = new StatementList(); addStatement(new IfStatement(not(nullTest), ifStmtList)); borrowStmtList(ifStmtList); try { return castToAssignableValueImpl(); } finally { returnStmtList(ifStmtList); } } else { return castToAssignableValueImpl(); } }
/** * Gets the right hand expression as a valid value to be assigned to the left hand side. Usually * returns the original rhs. However, if the lhs is of a primitive type, and the rhs is an * explicit null, returns a primitive value instead. */ private Expression rhsAsValue() { if (SqlTypeUtil.isJavaPrimitive(lhsType) && (rhsType.getSqlTypeName() == SqlTypeName.NULL)) { if (lhsType.getSqlTypeName() == SqlTypeName.BOOLEAN) { return Literal.constantFalse(); } else { return Literal.constantZero(); } } return rhsExp; }
/** Generates code to throw an exception when a NULL value is casted to a NOT NULL type */ private void checkNotNull() { if (!lhsType.isNullable() && rhsType.isNullable()) { rhsExp = rhsAsJava(); addStatement( new ExpressionStatement( new MethodCall( translator.getRelImplementor().getConnectionVariable(), "checkNotNull", new ExpressionList(Literal.makeLiteral(targetName), rhsExp)))); } }
/** * Implements a cast from any Java primitive to a nullable Java primitive as a simple * assignment. i.e. * * <pre> * [NullablePrimitiveType] lhs; * lhs.[nullIndicator] = ...; * if (! lhs.[nullIndicator]) { * // check overflow ... * // round ... * lhs.[value] = ...; * } * </pre> */ private Expression castPrimitiveToNullablePrimitive() { ensureLhs(); boolean nullableSource = rhsType.isNullable(); Expression rhsIsNull; if (nullableSource) { rhsIsNull = getNullIndicator(rhsExp); rhsExp = getValue(rhsType, rhsExp); } else { rhsIsNull = Literal.constantFalse(); } addStatement(assign(getNullIndicator(lhsExp), rhsIsNull)); StatementList setValueBlock = new StatementList(); StatementList oldList = borrowStmtList(setValueBlock); try { checkOverflow(); roundAsNeeded(); addStatement(assign(getValue(lhsType, lhsExp), new CastExpression(getLhsClass(), rhsExp))); } finally { returnStmtList(oldList); } if (nullableSource) { addStatement(new IfStatement(not(getNullIndicator(lhsExp)), setValueBlock)); } else { addStatementList(setValueBlock); } return lhsExp; }
/** * Checks for overflow when assigning one primitive type to another. Non-primitive types check * for overflow during assignment. */ private void checkOverflow() { String maxLiteral = null; String minLiteral = null; if (lhsType == null) { return; } // Assume that equivalent types can be assigned without overflow if (lhsType.getSqlTypeName() == rhsType.getSqlTypeName()) { return; } // Approximate numerics have a wider range than exact numerics if (SqlTypeUtil.isApproximateNumeric(lhsType) && SqlTypeUtil.isExactNumeric(rhsType)) { return; } // We can skip an error check if the left type is "larger" if (SqlTypeUtil.isIntType(lhsType) && SqlTypeUtil.isIntType(rhsType) && (SqlTypeUtil.maxValue(lhsType) >= SqlTypeUtil.maxValue(rhsType))) { return; } if (SqlTypeUtil.isExactNumeric(lhsType)) { String numClassName = SqlTypeUtil.getNumericJavaClassName(lhsType); minLiteral = numClassName + ".MIN_VALUE"; maxLiteral = numClassName + ".MAX_VALUE"; } else if (SqlTypeUtil.isApproximateNumeric(lhsType)) { String numClassName = SqlTypeUtil.getNumericJavaClassName(lhsType); maxLiteral = numClassName + ".MAX_VALUE"; minLiteral = "-" + maxLiteral; } if (maxLiteral == null) { return; } Statement ifstmt = new IfStatement( new BinaryExpression( new BinaryExpression( rhsExp, BinaryExpression.LESS, new Literal(Literal.STRING, minLiteral)), BinaryExpression.LOGICAL_OR, new BinaryExpression( rhsExp, BinaryExpression.GREATER, new Literal(Literal.STRING, maxLiteral))), getThrowStmtList()); addStatement(ifstmt); }
private boolean requiresSpecializedCast() { if ((rhsType != null) && (SqlTypeUtil.isNumeric(rhsType) || (rhsType.getSqlTypeName() == SqlTypeName.BOOLEAN)) && SqlTypeUtil.inCharOrBinaryFamilies(lhsType) && !SqlTypeUtil.isLob(lhsType)) { // Boolean or Numeric to String. // sometimes the Integer got slipped by. return true; } else { return false; } }
// implement FarragoOJRexImplementor public Expression implementFarrago( FarragoRexToOJTranslator translator, RexCall call, Expression[] operands) { RelDataType lhsType = call.getType(); RelDataType rhsType = call.operands[0].getType(); Expression rhsExp = operands[0]; SqlTypeName lhsTypeName = lhsType.getSqlTypeName(); if ((lhsTypeName == SqlTypeName.CURSOR) || (lhsTypeName == SqlTypeName.COLUMN_LIST)) { // Conversion should already have been taken care of outside. return rhsExp; } // NOTE jvs 19-Nov-2008: In some cases (e.g. FRG-273) a cast // may be illegal at the SQL level, but allowable as part of // implementation, so don't try to enforce // SqlTypeUtil.canCastFrom here. Anything which was supposed // to have been prevented should already have been caught // by the validator. CastHelper helper = new CastHelper(translator, null, call.toString(), lhsType, rhsType, null, rhsExp); return helper.implement(); }
/** * Implement the cast expression. * * <p>TODO: check for overflow * * @return the rhs expression casted as the lhs type */ public Expression implement() { // Check for invalid null assignment. Code generated afterwards // can assume null will never be assigned to a not null value. checkNotNull(); // Check for an explicit rhs null value. Code generated // afterwards need never check for an explicit null. if (rhsType.getSqlTypeName() == SqlTypeName.NULL) { if (lhsType.isNullable()) { return castFromNull(); } else { // NOTE jvs 27-Jan-2005: this code will never actually // be executed do to previous checkNotNull test, but // it still has to compile! return rhsAsValue(); } } // Case when left hand side is a nullable primitive if (translator.isNullablePrimitive(lhsType)) { if (SqlTypeUtil.isJavaPrimitive(rhsType) && (!rhsType.isNullable() || translator.isNullablePrimitive(rhsType))) { return castPrimitiveToNullablePrimitive(); } return castToAssignableValue(); } // Case when left hand side is a not nullable primitive if (SqlTypeUtil.isJavaPrimitive(lhsType)) { return castToNotNullPrimitive(); } // Case when left hand side is a structure if (lhsType.isStruct()) { assert (rhsType.isStruct()); // TODO jvs 27-May-2004: relax this assert and deal with // conversions, null checks, etc. assert (lhsType.equals(rhsType)); return getDirectAssignment(); } // Default is to treat non-primitives as AssignableValue return castToAssignableValue(); }
/** * Casts the rhs to a non nullable primitive value. Non nullable primitive values only have a * single value field. */ private Expression castToNotNullPrimitive() { // If the left and the right types are the same, perform a // trivial cast. if (lhsType == rhsType) { return getDirectAssignment(); } // Retrieve the value of the right side if it is a nullable // primitive or a Datetime or an Interval type. // TODO: is Decimal a nullable primitive? if (translator.isNullablePrimitive(rhsType) || SqlTypeUtil.isDatetime(rhsType) || SqlTypeUtil.isInterval(rhsType)) { rhsExp = getValue(rhsType, rhsExp); } // Get the name of the numeric class such as Byte, Short, etc. String numClassName = SqlTypeUtil.getNumericJavaClassName(lhsType); OJClass lhsClass = getLhsClass(); // When casting from a string (or binary) to a number, trim the // value and perform the cast by calling a class-specific parsing // function. if ((numClassName != null) && SqlTypeUtil.inCharOrBinaryFamilies(rhsType) && !SqlTypeUtil.isLob(rhsType)) { // TODO: toString will cause too much garbage collection. rhsExp = new MethodCall(rhsExp, "toString", new ExpressionList()); rhsExp = new MethodCall(rhsExp, "trim", new ExpressionList()); String methodName = "parse" + numClassName; if (lhsType.getSqlTypeName() == SqlTypeName.INTEGER) { methodName = "parseInt"; } rhsExp = new MethodCall( new Literal(Literal.STRING, numClassName), methodName, new ExpressionList(rhsExp)); Variable outTemp = translator.getRelImplementor().newVariable(); translator.addStatement( new VariableDeclaration( TypeName.forOJClass(lhsClass), new VariableDeclarator(outTemp.toString(), rhsExp))); rhsExp = outTemp; // Note: this check for overflow should only be required // when the string conversion does not perform a check. checkOverflow(); } else if ((lhsType.getSqlTypeName() == SqlTypeName.BOOLEAN) && SqlTypeUtil.inCharOrBinaryFamilies(rhsType) && !SqlTypeUtil.isLob(rhsType)) { // Casting from string to boolean relies on the runtime type. // Note: string is trimmed by conversion method. // TODO: toString will cause too much garbage collection. Expression str = new MethodCall(rhsExp, "toString", new ExpressionList()); rhsExp = new MethodCall( OJClass.forClass(NullablePrimitive.NullableBoolean.class), "convertString", new ExpressionList(str)); } else { // In general, check for overflow checkOverflow(); } roundAsNeeded(); rhsExp = new CastExpression(lhsClass, rhsExp); return getDirectAssignment(); }
private Expression castToAssignableValueImpl() { if (requiresSpecializedCast()) { if (rhsType.isNullable() && (!SqlTypeUtil.isDecimal(rhsType))) { rhsExp = getValue(rhsType, rhsExp); } addStatement( new ExpressionStatement( new MethodCall( lhsExp, "cast", new ExpressionList(rhsExp, Literal.makeLiteral(lhsType.getPrecision()))))); } else { // Set current_date for casting time to timestamp. If // rhsType is null then we may have to be ready for anything. // But it will be null even for current_timestamp, so the // condition below seems a bit excessive. if ((lhsType.getSqlTypeName() == SqlTypeName.TIMESTAMP) && ((rhsType == null) || (rhsType.getSqlTypeName() == SqlTypeName.TIME))) { addStatement( new ExpressionStatement( new MethodCall(lhsExp, "setCurrentDate", new ExpressionList(getCurrentDate())))); } addStatement( new ExpressionStatement( new MethodCall( lhsExp, AssignableValue.ASSIGNMENT_METHOD_NAME, new ExpressionList(rhsExp)))); } // Trim precision of datetime values. // if (((lhsType.getSqlTypeName() == SqlTypeName.TIMESTAMP) || (lhsType.getSqlTypeName() == SqlTypeName.TIME))) { if ((rhsType != null) // FIXME: JavaType(java.sql.Time) and // JavaType(java.sql.Timestamp) say they support precision // but do not. && !rhsType.toString().startsWith("JavaType(") && rhsType.getSqlTypeName().allowsPrec() && (lhsType.getPrecision() < rhsType.getPrecision())) { int lhsPrecision = lhsType.getPrecision(); if (lhsPrecision == -1) { lhsPrecision = 0; } addStatement( new ExpressionStatement( new MethodCall( lhsExp, SqlDateTimeWithoutTZ.ADJUST_PRECISION_METHOD_NAME, new ExpressionList(Literal.makeLiteral(lhsPrecision))))); } } boolean mayNeedPadOrTruncate = false; if (SqlTypeUtil.inCharOrBinaryFamilies(lhsType) && !SqlTypeUtil.isLob(lhsType)) { mayNeedPadOrTruncate = true; } if (mayNeedPadOrTruncate) { // check overflow if it is datetime. // TODO: should check it at the run time. // so, it should be in the // cast(SqlDateTimeWithTZ, int precision); if ((rhsType != null) && (rhsType.getSqlTypeName() != null)) { SqlTypeName typeName = rhsType.getSqlTypeName(); int precision = 0; switch (typeName) { case DATE: precision = 10; break; case TIME: precision = 8; break; case TIMESTAMP: precision = 19; break; } if ((precision != 0) && (precision > lhsType.getPrecision())) { addStatement( new IfStatement( new BinaryExpression( Literal.makeLiteral(precision), BinaryExpression.GREATER, Literal.makeLiteral(lhsType.getPrecision())), getThrowStmtList())); } } if ((rhsType != null) && (rhsType.getFamily() == lhsType.getFamily()) && !SqlTypeUtil.isLob(rhsType)) { // we may be able to skip pad/truncate based on // known facts about source and target precisions if (SqlTypeUtil.isBoundedVariableWidth(lhsType)) { if (lhsType.getPrecision() >= rhsType.getPrecision()) { // target precision is greater than source // precision, so truncation is impossible // and we can skip adjustment return lhsExp; } } else { if ((lhsType.getPrecision() == rhsType.getPrecision()) && !SqlTypeUtil.isBoundedVariableWidth(rhsType)) { // source and target are both fixed-width, and // precisions are the same, so there's no adjustment // needed return lhsExp; } } } // determine target precision Expression precisionExp = Literal.makeLiteral(lhsType.getPrecision()); // need to pad only for fixed width Expression needPadExp = Literal.makeLiteral(!SqlTypeUtil.isBoundedVariableWidth(lhsType)); // pad character is 0 for binary, space for character Expression padByteExp; if (!SqlTypeUtil.inCharFamily(lhsType)) { padByteExp = new CastExpression(OJSystem.BYTE, Literal.makeLiteral(0)); } else { padByteExp = new CastExpression(OJSystem.BYTE, Literal.makeLiteral(' ')); } // generate the call to do the job addStatement( new ExpressionStatement( new MethodCall( lhsExp, BytePointer.ENFORCE_PRECISION_METHOD_NAME, new ExpressionList(precisionExp, needPadExp, padByteExp)))); } return lhsExp; }