/** * Returns true if a type is a simple cast of another type. It is if the cast type is nullable and * the cast is one of the following: * <li>x TO x * <li>char(n) TO varchar(m) * <li>varchar(n) TO varchar(m) * <li>x not null TO x nullable * * @param origType original type passed into the cast operand * @param castType type the operand will be casted to * @return true if the cast is simple */ private boolean isCastSimple(RelDataType origType, RelDataType castType) { SqlTypeName origTypeName = origType.getSqlTypeName(); SqlTypeName castTypeName = castType.getSqlTypeName(); if (!(castType.isNullable())) { return false; } Charset origCharset = origType.getCharset(); Charset castCharset = castType.getCharset(); if ((origCharset != null) || (castCharset != null)) { if ((origCharset == null) || (castCharset == null)) { return false; } if (!origCharset.equals(castCharset)) { return false; } } return ((origType == castType) || ((origTypeName == SqlTypeName.CHAR) && (castTypeName == SqlTypeName.VARCHAR)) || ((origTypeName == SqlTypeName.VARCHAR) && (castTypeName == SqlTypeName.VARCHAR)) || ((origTypeName == castTypeName) && (origType.getPrecision() == castType.getPrecision()) && ((origTypeName != SqlTypeName.DECIMAL) || (origType.getScale() == castType.getScale())) && (!origType.isNullable() && castType.isNullable()))); }
/** * 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; }
public int getFieldJdbcType(int fieldOrdinal) { RelDataType type = getFieldNamedType(fieldOrdinal); SqlTypeName typeName = type.getSqlTypeName(); if (typeName == null) { return Types.OTHER; } return typeName.getJdbcOrdinal(); }
/** * 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); }
public RelDataType getFieldType(int fieldOrdinal) { RelDataType namedType = getFieldNamedType(fieldOrdinal); if (namedType.getSqlTypeName() == SqlTypeName.DISTINCT) { // for most metadata calls, report information about the // predefined type on which the distinct type is based return namedType.getFields()[0].getType(); } else { return namedType; } }
public int getFieldScale(int fieldOrdinal) { RelDataType type = getFieldType(fieldOrdinal); SqlTypeName typeName = type.getSqlTypeName(); if (typeName == null) { return 0; } if (typeName.allowsPrecScale(true, true)) { return type.getScale(); } else { return 0; } }
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; } }
public String getFieldTypeName(int fieldOrdinal) { RelDataType type = getFieldNamedType(fieldOrdinal); SqlTypeName typeName = type.getSqlTypeName(); if (typeName == null) { return type.toString(); } switch (typeName) { case STRUCTURED: case DISTINCT: return type.getSqlIdentifier().toString(); case INTERVAL_DAY_TIME: case INTERVAL_YEAR_MONTH: return type.toString(); } return typeName.name(); }
/** * 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(); }
private SqlOperator toOp(SqlIdentifier name, Function function) { List<RelDataType> argTypes = new ArrayList<RelDataType>(); List<SqlTypeFamily> typeFamilies = new ArrayList<SqlTypeFamily>(); for (FunctionParameter o : function.getParameters()) { final RelDataType type = o.getType(typeFactory); argTypes.add(type); typeFamilies.add(Util.first(type.getSqlTypeName().getFamily(), SqlTypeFamily.ANY)); } final RelDataType returnType; if (function instanceof ScalarFunction) { return new SqlUserDefinedFunction( name, ReturnTypes.explicit(Schemas.proto((ScalarFunction) function)), InferTypes.explicit(argTypes), OperandTypes.family(typeFamilies), toSql(argTypes), function); } else if (function instanceof AggregateFunction) { returnType = ((AggregateFunction) function).getReturnType(typeFactory); return new SqlUserDefinedAggFunction( name, ReturnTypes.explicit(returnType), InferTypes.explicit(argTypes), OperandTypes.family(typeFamilies), (AggregateFunction) function); } else if (function instanceof TableMacro) { return new SqlUserDefinedTableMacro( name, ReturnTypes.CURSOR, InferTypes.explicit(argTypes), OperandTypes.family(typeFamilies), (TableMacro) function); } else if (function instanceof TableFunction) { return new SqlUserDefinedTableFunction( name, ReturnTypes.CURSOR, InferTypes.explicit(argTypes), OperandTypes.family(typeFamilies), toSql(argTypes), (TableFunction) function); } else { throw new AssertionError("unknown function type " + function); } }
/** * Determines whether a {@link RexCall} requires decimal expansion. It usually requires expansion * if it has decimal operands. * * <p>Exceptions to this rule are: * * <ul> * <li>isNull doesn't require expansion * <li>It's okay to cast decimals to and from char types * <li>It's okay to cast nulls as decimals * <li>Casts require expansion if their return type is decimal * <li>Reinterpret casts can handle a decimal operand * </ul> * * @param expr expression possibly in need of expansion * @param recurse whether to check nested calls * @return whether the expression requires expansion */ public static boolean requiresDecimalExpansion(RexNode expr, boolean recurse) { if (!(expr instanceof RexCall)) { return false; } RexCall call = (RexCall) expr; boolean localCheck = true; switch (call.getKind()) { case Reinterpret: case IsNull: localCheck = false; break; case Cast: RelDataType lhsType = call.getType(); RelDataType rhsType = call.operands[0].getType(); if (rhsType.getSqlTypeName() == SqlTypeName.NULL) { return false; } if (SqlTypeUtil.inCharFamily(lhsType) || SqlTypeUtil.inCharFamily(rhsType)) { localCheck = false; } else if (SqlTypeUtil.isDecimal(lhsType) && (lhsType != rhsType)) { return true; } break; default: localCheck = call.getOperator().requiresDecimalExpansion(); } if (localCheck) { if (SqlTypeUtil.isDecimal(call.getType())) { // NOTE jvs 27-Mar-2007: Depending on the type factory, the // result of a division may be decimal, even though both inputs // are integer. return true; } for (int i = 0; i < call.operands.length; i++) { if (SqlTypeUtil.isDecimal(call.operands[i].getType())) { return true; } } } return (recurse && requiresDecimalExpansion(call.operands, recurse)); }
// 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(); }
/** * 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; }