/** * 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 round an expression according to the Farrago convention. The Farrago * convention is to round away from zero. Rounding is performed with the following algorithm. * * <pre> * in = rhs; * if (value < 0) { * in = -in; * out = Math.round(in); * out = -out; * } else { * out = Math.round(in); * } * </pre> * * <p>PRECONDITION: rhsExp must be an unwrapped (not null) Java primitive * * <p>TODO: account for overflow in both unary minus and round. */ private Expression roundAway() { // Get the primitive part of right hand side RelDataType inType = translator.getTypeFactory().createTypeWithNullability(rhsType, false); // TODO: is there any preference between stack and instance var? OJClass inClass = getClass(inType); Variable inTemp = translator.getRelImplementor().newVariable(); translator.addStatement(declareStackVar(inClass, inTemp, rhsExp)); OJClass outClass = getLhsClass(); Variable outTemp = translator.getRelImplementor().newVariable(); translator.addStatement(declareStackVar(outClass, outTemp, null)); boolean isLong = translator.getFarragoTypeFactory().getClassForPrimitive(lhsType) == long.class; addStatement( new IfStatement( new BinaryExpression(inTemp, BinaryExpression.LESS, Literal.constantZero()), new StatementList( assign(inTemp, minus(inClass, inTemp)), assign(outTemp, round(lhsClass, inTemp, isLong)), assign(outTemp, minus(outClass, outTemp))), new StatementList(assign(outTemp, round(lhsClass, inTemp, isLong))))); return outTemp; }
/** * 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; }
/** 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)))); } }
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; }
/** * Generates code for a Java expression satisfying the {@link org.eigenbase.runtime.TupleIter} * interface. The generated code allocates a {@link org.eigenbase.runtime.CalcTupleIter} with a * dynamic {@link org.eigenbase.runtime.TupleIter#fetchNext()} method. If the "abort on error" * flag is false, or an error handling tag is specified, then fetchNext is written to handle row * errors. * * <p>Row errors are handled by wrapping expressions that can fail with a try/catch block. A * caught RuntimeException is then published to an "connection variable." In the event that errors * can overflow, an "error buffering" flag allows them to be posted again on the next iteration of * fetchNext. * * @param implementor an object that implements relations as Java code * @param rel the relation to be implemented * @param childExp the implemented child of the relation * @param varInputRow the Java variable to use for the input row * @param inputRowType the rel data type of the input row * @param outputRowType the rel data type of the output row * @param program the rex program to implemented by the relation * @param tag an error handling tag * @return a Java expression satisfying the TupleIter interface */ public static Expression implementAbstractTupleIter( JavaRelImplementor implementor, JavaRel rel, Expression childExp, Variable varInputRow, final RelDataType inputRowType, final RelDataType outputRowType, RexProgram program, String tag) { MemberDeclarationList memberList = new MemberDeclarationList(); // Perform error recovery if continuing on errors or if // an error handling tag has been specified boolean errorRecovery = !abortOnError || (tag != null); // Error buffering should not be enabled unless error recovery is assert !errorBuffering || errorRecovery; // Allow backwards compatibility until all Farrago extensions are // satisfied with the new error handling semantics. The new semantics // include: // (1) cast input object to input row object outside of try block, // should be fine, at least for base Farrago // (2) maintain a columnIndex counter to better locate of error, // at the cost of a few cycles // (3) publish errors to the runtime context. FarragoRuntimeContext // now supports this API boolean backwardsCompatible = true; if (tag != null) { backwardsCompatible = false; } RelDataTypeFactory typeFactory = implementor.getTypeFactory(); OJClass outputRowClass = OJUtil.typeToOJClass(outputRowType, typeFactory); OJClass inputRowClass = OJUtil.typeToOJClass(inputRowType, typeFactory); Variable varOutputRow = implementor.newVariable(); FieldDeclaration inputRowVarDecl = new FieldDeclaration( new ModifierList(ModifierList.PRIVATE), TypeName.forOJClass(inputRowClass), varInputRow.toString(), null); FieldDeclaration outputRowVarDecl = new FieldDeclaration( new ModifierList(ModifierList.PRIVATE), TypeName.forOJClass(outputRowClass), varOutputRow.toString(), new AllocationExpression(outputRowClass, new ExpressionList())); // The method body for fetchNext, a main target of code generation StatementList nextMethodBody = new StatementList(); // First, post an error if it overflowed the previous time // if (pendingError) { // rc = handleRowError(...); // if (rc instanceof NoDataReason) { // return rc; // } // pendingError = false; // } if (errorBuffering) { // add to next method body... } // Most of fetchNext falls within a while() block. The while block // allows us to try multiple input rows against a filter condition // before returning a single row. // while (true) { // Object varInputObj = inputIterator.fetchNext(); // if (varInputObj instanceof TupleIter.NoDataReason) { // return varInputObj; // } // varInputRow = (InputRowClass) varInputObj; // int columnIndex = 0; // [calculation statements] // } StatementList whileBody = new StatementList(); Variable varInputObj = implementor.newVariable(); whileBody.add( new VariableDeclaration( OJUtil.typeNameForClass(Object.class), varInputObj.toString(), new MethodCall(new FieldAccess("inputIterator"), "fetchNext", new ExpressionList()))); StatementList ifNoDataReasonBody = new StatementList(); whileBody.add( new IfStatement( new InstanceofExpression( varInputObj, OJUtil.typeNameForClass(TupleIter.NoDataReason.class)), ifNoDataReasonBody)); ifNoDataReasonBody.add(new ReturnStatement(varInputObj)); // Push up the row declaration for new error handling so that the // input row is available to the error handler if (!backwardsCompatible) { whileBody.add(assignInputRow(inputRowClass, varInputRow, varInputObj)); } Variable varColumnIndex = null; if (errorRecovery && !backwardsCompatible) { // NOTE jvs 7-Oct-2006: Declare varColumnIndex as a member // (rather than a local) in case in the future we want // to decompose complex expressions into helper methods. varColumnIndex = implementor.newVariable(); FieldDeclaration varColumnIndexDecl = new FieldDeclaration( new ModifierList(ModifierList.PRIVATE), OJUtil.typeNameForClass(int.class), varColumnIndex.toString(), null); memberList.add(varColumnIndexDecl); whileBody.add( new ExpressionStatement( new AssignmentExpression( varColumnIndex, AssignmentExpression.EQUALS, Literal.makeLiteral(0)))); } // Calculator (projection, filtering) statements are later appended // to calcStmts. Typically, this target will be the while list itself. StatementList calcStmts; if (!errorRecovery) { calcStmts = whileBody; } else { // For error recovery, we wrap the calc statements // (e.g., everything but the code that reads rows from the // inputIterator) in a try/catch that publishes exceptions. calcStmts = new StatementList(); // try { /* calcStmts */ } // catch(RuntimeException ex) { // Object rc = connection.handleRowError(...); // [buffer error if necessary] // } StatementList catchStmts = new StatementList(); if (backwardsCompatible) { catchStmts.add( new ExpressionStatement( new MethodCall( new MethodCall( OJUtil.typeNameForClass(EigenbaseTrace.class), "getStatementTracer", null), "log", new ExpressionList( new FieldAccess(OJUtil.typeNameForClass(Level.class), "WARNING"), Literal.makeLiteral("java calc exception"), new FieldAccess("ex"))))); } else { Variable varRc = implementor.newVariable(); ExpressionList handleRowErrorArgs = new ExpressionList(varInputRow, new FieldAccess("ex"), varColumnIndex); handleRowErrorArgs.add(Literal.makeLiteral(tag)); catchStmts.add( new VariableDeclaration( OJUtil.typeNameForClass(Object.class), varRc.toString(), new MethodCall( implementor.getConnectionVariable(), "handleRowError", handleRowErrorArgs))); // Buffer an error if it overflowed // if (rc instanceof NoDataReason) { // pendingError = true; // [save error state] // return rc; // } if (errorBuffering) { // add to catch statements... } } CatchList catchList = new CatchList( new CatchBlock( new Parameter(OJUtil.typeNameForClass(RuntimeException.class), "ex"), catchStmts)); TryStatement tryStmt = new TryStatement(calcStmts, catchList); whileBody.add(tryStmt); } if (backwardsCompatible) { calcStmts.add(assignInputRow(inputRowClass, varInputRow, varInputObj)); } StatementList condBody; RexToOJTranslator translator = implementor.newStmtTranslator(rel, calcStmts, memberList); try { translator.pushProgram(program); if (program.getCondition() != null) { // TODO jvs 8-Oct-2006: move condition to its own // method if big, as below for project exprs. condBody = new StatementList(); RexNode rexIsTrue = rel.getCluster() .getRexBuilder() .makeCall(SqlStdOperatorTable.isTrueOperator, program.getCondition()); Expression conditionExp = translator.translateRexNode(rexIsTrue); calcStmts.add(new IfStatement(conditionExp, condBody)); } else { condBody = calcStmts; } RelDataTypeField[] fields = outputRowType.getFields(); final List<RexLocalRef> projectRefList = program.getProjectList(); int i = -1; for (RexLocalRef rhs : projectRefList) { // NOTE jvs 14-Sept-2006: Put complicated project expressions // into their own method, otherwise a big select list can easily // blow the 64K Java limit on method bytecode size. Make // methods private final in the hopes that they will get inlined // JIT. For now we decide "complicated" based on the size of // the generated Java parse tree. A big enough select list of // simple expressions could still blow the limit, so we may need // to group them together, sub-divide, etc. StatementList projMethodBody = new StatementList(); if (errorRecovery && !backwardsCompatible) { projMethodBody.add( new ExpressionStatement( new UnaryExpression(varColumnIndex, UnaryExpression.POST_INCREMENT))); } ++i; RexToOJTranslator projTranslator = translator.push(projMethodBody); String javaFieldName = Util.toJavaId(fields[i].getName(), i); Expression lhs = new FieldAccess(varOutputRow, javaFieldName); projTranslator.translateAssignment(fields[i], lhs, rhs); int complexity = OJUtil.countParseTreeNodes(projMethodBody); // REVIEW: HCP 5/18/2011 // The projMethod should be checked // for causing possible compiler errors caused by the use of // variables declared in other projMethods. Also the // local declaration of variabled used by other proj methods // should also be checked. // Fixing for backswing integration 14270 // TODO: check if abstracting this method body will cause // a compiler error if (true) { // No method needed; just append. condBody.addAll(projMethodBody); continue; } // Need a separate method. String projMethodName = "calc_" + varOutputRow.toString() + "_f_" + i; MemberDeclaration projMethodDecl = new MethodDeclaration( new ModifierList(ModifierList.PRIVATE | ModifierList.FINAL), TypeName.forOJClass(OJSystem.VOID), projMethodName, new ParameterList(), null, projMethodBody); memberList.add(projMethodDecl); condBody.add(new ExpressionStatement(new MethodCall(projMethodName, new ExpressionList()))); } } finally { translator.popProgram(program); } condBody.add(new ReturnStatement(varOutputRow)); WhileStatement whileStmt = new WhileStatement(Literal.makeLiteral(true), whileBody); nextMethodBody.add(whileStmt); MemberDeclaration fetchNextMethodDecl = new MethodDeclaration( new ModifierList(ModifierList.PUBLIC), OJUtil.typeNameForClass(Object.class), "fetchNext", new ParameterList(), null, nextMethodBody); // The restart() method should reset variables used to buffer errors // pendingError = false if (errorBuffering) { // declare refinement of restart() and add to member list... } memberList.add(inputRowVarDecl); memberList.add(outputRowVarDecl); memberList.add(fetchNextMethodDecl); Expression newTupleIterExp = new AllocationExpression( OJUtil.typeNameForClass(CalcTupleIter.class), new ExpressionList(childExp), memberList); return newTupleIterExp; }