/** * 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()))); }
private SqlNode toSql(RelDataType type) { if (dialect.getDatabaseProduct() == SqlDialect.DatabaseProduct.MYSQL) { final SqlTypeName sqlTypeName = type.getSqlTypeName(); switch (sqlTypeName) { case VARCHAR: // MySQL doesn't have a VARCHAR type, only CHAR. return new SqlDataTypeSpec( new SqlIdentifier("CHAR", POS), type.getPrecision(), -1, null, null, POS); case INTEGER: return new SqlDataTypeSpec( new SqlIdentifier("_UNSIGNED", POS), type.getPrecision(), -1, null, null, POS); } } if (type instanceof BasicSqlType) { return new SqlDataTypeSpec( new SqlIdentifier(type.getSqlTypeName().name(), POS), type.getPrecision(), type.getScale(), type.getCharset() != null && dialect.supportsCharSet() ? type.getCharset().name() : null, null, POS); } throw new AssertionError(type); // TODO: implement }
/** * 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; }
/** @pre SqlTypeUtil.sameNamedType(argTypes[0], (argTypes[1])) */ public RelDataType inferReturnType(SqlOperatorBinding opBinding) { final RelDataType argType0 = opBinding.getOperandType(0); final RelDataType argType1 = opBinding.getOperandType(1); if (!(SqlTypeUtil.inCharOrBinaryFamilies(argType0) && SqlTypeUtil.inCharOrBinaryFamilies(argType1))) { Util.pre( SqlTypeUtil.sameNamedType(argType0, argType1), "SqlTypeUtil.sameNamedType(argTypes[0], argTypes[1])"); } SqlCollation pickedCollation = null; if (SqlTypeUtil.inCharFamily(argType0)) { if (!SqlTypeUtil.isCharTypeComparable(opBinding.collectOperandTypes().subList(0, 2))) { throw opBinding.newError( RESOURCE.typeNotComparable( argType0.getFullTypeString(), argType1.getFullTypeString())); } pickedCollation = SqlCollation.getCoercibilityDyadicOperator( argType0.getCollation(), argType1.getCollation()); assert null != pickedCollation; } // Determine whether result is variable-length SqlTypeName typeName = argType0.getSqlTypeName(); if (SqlTypeUtil.isBoundedVariableWidth(argType1)) { typeName = argType1.getSqlTypeName(); } RelDataType ret; ret = opBinding .getTypeFactory() .createSqlType(typeName, argType0.getPrecision() + argType1.getPrecision()); if (null != pickedCollation) { RelDataType pickedType; if (argType0.getCollation().equals(pickedCollation)) { pickedType = argType0; } else if (argType1.getCollation().equals(pickedCollation)) { pickedType = argType1; } else { throw Util.newInternal("should never come here"); } ret = opBinding .getTypeFactory() .createTypeWithCharsetAndCollation( ret, pickedType.getCharset(), pickedType.getCollation()); } return ret; }
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(); }
<T> PrepareResult<T> prepare_( Context context, String sql, Queryable<T> queryable, Type elementType) { final JavaTypeFactory typeFactory = context.getTypeFactory(); OptiqCatalogReader catalogReader = new OptiqCatalogReader(context.getRootSchema(), typeFactory); final OptiqPreparingStmt preparingStmt = new OptiqPreparingStmt(catalogReader, typeFactory, context.getRootSchema()); preparingStmt.setResultCallingConvention(CallingConvention.ENUMERABLE); final RelDataType x; final PreparedResult preparedResult; if (sql != null) { assert queryable == null; SqlParser parser = new SqlParser(sql); SqlNode sqlNode; try { sqlNode = parser.parseQuery(); } catch (SqlParseException e) { throw new RuntimeException("parse failed", e); } final Schema rootSchema = context.getRootSchema(); SqlValidator validator = new SqlValidatorImpl( new ChainedSqlOperatorTable( Arrays.<SqlOperatorTable>asList( SqlStdOperatorTable.instance(), new MySqlOperatorTable(rootSchema, typeFactory))), catalogReader, typeFactory, SqlConformance.Default) {}; preparedResult = preparingStmt.prepareSql(sqlNode, Object.class, validator, true); x = validator.getValidatedNodeType(sqlNode); } else { assert queryable != null; x = context.getTypeFactory().createType(elementType); preparedResult = preparingStmt.prepareQueryable(queryable, x); } // TODO: parameters final List<Parameter> parameters = Collections.emptyList(); // TODO: column meta data final List<ColumnMetaData> columns = new ArrayList<ColumnMetaData>(); RelDataType jdbcType = makeStruct(typeFactory, x); for (RelDataTypeField field : jdbcType.getFields()) { RelDataType type = field.getType(); SqlTypeName sqlTypeName = type.getSqlTypeName(); columns.add( new ColumnMetaData( columns.size(), false, true, false, false, type.isNullable() ? 1 : 0, true, 0, field.getName(), field.getName(), null, sqlTypeName.allowsPrec() && false ? type.getPrecision() : -1, sqlTypeName.allowsScale() ? type.getScale() : -1, null, null, sqlTypeName.getJdbcOrdinal(), sqlTypeName.getName(), true, false, false, null)); } return new PrepareResult<T>(sql, parameters, columns, (Enumerable<T>) preparedResult.execute()); }
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; }
/** * 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(); }
/** Converts an expression from {@link RexNode} to {@link SqlNode} format. */ SqlNode toSql(RexProgram program, RexNode rex) { if (rex instanceof RexLocalRef) { final int index = ((RexLocalRef) rex).getIndex(); return toSql(program, program.getExprList().get(index)); } else if (rex instanceof RexInputRef) { return field(((RexInputRef) rex).getIndex()); } else if (rex instanceof RexLiteral) { final RexLiteral literal = (RexLiteral) rex; switch (literal.getTypeName().getFamily()) { case CHARACTER: return SqlLiteral.createCharString((String) literal.getValue2(), POS); case NUMERIC: case EXACT_NUMERIC: return SqlLiteral.createExactNumeric(literal.getValue().toString(), POS); case APPROXIMATE_NUMERIC: return SqlLiteral.createApproxNumeric(literal.getValue().toString(), POS); case BOOLEAN: return SqlLiteral.createBoolean((Boolean) literal.getValue(), POS); case DATE: return SqlLiteral.createDate((Calendar) literal.getValue(), POS); case TIME: return SqlLiteral.createTime( (Calendar) literal.getValue(), literal.getType().getPrecision(), POS); case TIMESTAMP: return SqlLiteral.createTimestamp( (Calendar) literal.getValue(), literal.getType().getPrecision(), POS); case ANY: switch (literal.getTypeName()) { case NULL: return SqlLiteral.createNull(POS); // fall through } default: throw new AssertionError(literal + ": " + literal.getTypeName()); } } else if (rex instanceof RexCall) { final RexCall call = (RexCall) rex; final SqlOperator op = call.getOperator(); final List<SqlNode> nodeList = toSql(program, call.getOperands()); if (op == SqlStdOperatorTable.CAST) { RelDataType type = call.getType(); if (type.getSqlTypeName() == SqlTypeName.VARCHAR && dialect.getDatabaseProduct() == SqlDialect.DatabaseProduct.MYSQL) { // MySQL doesn't have a VARCHAR type, only CHAR. nodeList.add( new SqlDataTypeSpec( new SqlIdentifier("CHAR", POS), type.getPrecision(), -1, null, null, POS)); } else { nodeList.add(toSql(type)); } } if (op == SqlStdOperatorTable.CASE) { final SqlNode valueNode; final List<SqlNode> whenList = Expressions.list(); final List<SqlNode> thenList = Expressions.list(); final SqlNode elseNode; if (nodeList.size() % 2 == 0) { // switched: // "case x when v1 then t1 when v2 then t2 ... else e end" valueNode = nodeList.get(0); for (int i = 1; i < nodeList.size() - 1; i += 2) { whenList.add(nodeList.get(i)); thenList.add(nodeList.get(i + 1)); } } else { // other: "case when w1 then t1 when w2 then t2 ... else e end" valueNode = null; for (int i = 0; i < nodeList.size() - 1; i += 2) { whenList.add(nodeList.get(i)); thenList.add(nodeList.get(i + 1)); } } elseNode = nodeList.get(nodeList.size() - 1); return op.createCall( POS, valueNode, new SqlNodeList(whenList, POS), new SqlNodeList(thenList, POS), elseNode); } if (op instanceof SqlBinaryOperator && nodeList.size() > 2) { // In RexNode trees, OR and AND have any number of children; // SqlCall requires exactly 2. So, convert to a left-deep binary tree. return createLeftCall(op, nodeList); } return op.createCall(new SqlNodeList(nodeList, POS)); } else { throw new AssertionError(rex); } }