public void onMatch(RelOptRuleCall call) { CalcRel calc = (CalcRel) call.getRels()[0]; RexProgram program = calc.getProgram(); final List<RexNode> exprList = program.getExprList(); // Form a list of expressions with sub-expressions fully // expanded. final List<RexNode> expandedExprList = new ArrayList<RexNode>(exprList.size()); final RexShuttle shuttle = new RexShuttle() { public RexNode visitLocalRef(RexLocalRef localRef) { return expandedExprList.get(localRef.getIndex()); } }; for (RexNode expr : exprList) { expandedExprList.add(expr.accept(shuttle)); } if (reduceExpressions(calc, expandedExprList)) { final RexProgramBuilder builder = new RexProgramBuilder( calc.getChild().getRowType(), calc.getCluster().getRexBuilder()); List<RexLocalRef> list = new ArrayList<RexLocalRef>(); for (RexNode expr : expandedExprList) { list.add(builder.registerInput(expr)); } if (program.getCondition() != null) { final int conditionIndex = program.getCondition().getIndex(); final RexNode newConditionExp = expandedExprList.get(conditionIndex); if (newConditionExp.isAlwaysTrue()) { // condition is always TRUE - drop it } else if ((newConditionExp instanceof RexLiteral) || RexUtil.isNullLiteral(newConditionExp, true)) { // condition is always NULL or FALSE - replace calc // with empty call.transformTo(new EmptyRel(calc.getCluster(), calc.getRowType())); return; } else { builder.addCondition(list.get(conditionIndex)); } } int k = 0; for (RexLocalRef projectExpr : program.getProjectList()) { final int index = projectExpr.getIndex(); builder.addProject( list.get(index).getIndex(), program.getOutputRowType().getFieldList().get(k++).getName()); } call.transformTo( new CalcRel( calc.getCluster(), calc.getTraits(), calc.getChild(), calc.getRowType(), builder.getProgram(), calc.getCollationList())); // New plan is absolutely better than old plan. call.getPlanner().setImportance(calc, 0.0); } }
/** * Generates specific XML (sometimes called 'attribute-oriented XML'). Like this: * * <pre> * <Join condition="EMP.DEPTNO = DEPT.DEPTNO"> * <Project expr1="x + y" expr2="42"> * <TableAccess table="SALES.EMPS"> * </Join> * </pre> * * @param rel Relational expression * @param terms Names of the attributes of the plan * @param values Values of the attributes of the plan */ private void explainSpecific(RelNode rel, String[] terms, Object[] values) { RelNode[] inputs = rel.getInputs(); RexNode[] children = rel.getChildExps(); assert terms.length == (inputs.length + children.length + values.length) : "terms.length=" + terms.length + " inputs.length=" + inputs.length + " children.length=" + children.length + " values.length=" + values.length; String tagName = rel.getRelTypeName(); xmlOutput.beginBeginTag(tagName); xmlOutput.attribute("id", rel.getId() + ""); int j = 0; for (int i = 0; i < children.length; i++) { RexNode child = children[i]; xmlOutput.attribute(terms[inputs.length + j++], child.toString()); } for (int i = 0; i < values.length; i++) { Object value = values[i]; if (value != null) { xmlOutput.attribute(terms[inputs.length + j++], value.toString()); } } xmlOutput.endBeginTag(tagName); level++; for (int i = 0; i < inputs.length; i++) { RelNode child = inputs[i]; child.explain(this); } level--; }
public void collectVariablesUsed(Set<String> variableSet) { final RelOptUtil.VariableUsedVisitor vuv = new RelOptUtil.VariableUsedVisitor(); for (RexNode expr : program.getExprList()) { expr.accept(vuv); } variableSet.addAll(vuv.variables); }
private Expression translate0(RexNode expr) { if (expr instanceof RexInputRef) { // TODO: multiple inputs, e.g. joins final Expression input = getInput(0); final int index = ((RexInputRef) expr).getIndex(); final List<RelDataTypeField> fields = program.getInputRowType().getFieldList(); final RelDataTypeField field = fields.get(index); if (fields.size() == 1) { return input; } else if (input.getType() == Object[].class) { return Expressions.convert_( Expressions.arrayIndex(input, Expressions.constant(field.getIndex())), Types.box(JavaRules.EnumUtil.javaClass(typeFactory, field.getType()))); } else { return Expressions.field(input, field.getName()); } } if (expr instanceof RexLocalRef) { return translate(program.getExprList().get(((RexLocalRef) expr).getIndex())); } if (expr instanceof RexLiteral) { return Expressions.constant( ((RexLiteral) expr).getValue(), typeFactory.getJavaClass(expr.getType())); } if (expr instanceof RexCall) { final RexCall call = (RexCall) expr; final SqlOperator operator = call.getOperator(); final ExpressionType expressionType = SQL_TO_LINQ_OPERATOR_MAP.get(operator); if (expressionType != null) { switch (operator.getSyntax()) { case Binary: return Expressions.makeBinary( expressionType, translate(call.getOperands()[0]), translate(call.getOperands()[1])); case Postfix: case Prefix: return Expressions.makeUnary(expressionType, translate(call.getOperands()[0])); default: throw new RuntimeException("unknown syntax " + operator.getSyntax()); } } Method method = SQL_OP_TO_JAVA_METHOD_MAP.get(operator); if (method != null) { List<Expression> exprs = translateList(Arrays.asList(call.operands)); return !Modifier.isStatic(method.getModifiers()) ? Expressions.call(exprs.get(0), method, exprs.subList(1, exprs.size())) : Expressions.call(method, exprs); } switch (expr.getKind()) { default: throw new RuntimeException("cannot translate expression " + expr); } } throw new RuntimeException("cannot translate expression " + expr); }
public Boolean areColumnsUnique(ProjectRelBase rel, BitSet columns, boolean ignoreNulls) { // ProjectRel maps a set of rows to a different set; // Without knowledge of the mapping function(whether it // preserves uniqueness), it is only safe to derive uniqueness // info from the child of a project when the mapping is f(a) => a. // // Also need to map the input column set to the corresponding child // references List<RexNode> projExprs = rel.getProjects(); BitSet childColumns = new BitSet(); for (int bit : BitSets.toIter(columns)) { RexNode projExpr = projExprs.get(bit); if (projExpr instanceof RexInputRef) { childColumns.set(((RexInputRef) projExpr).getIndex()); } else if (projExpr instanceof RexCall && ignoreNulls) { // If the expression is a cast such that the types are the same // except for the nullability, then if we're ignoring nulls, // it doesn't matter whether the underlying column reference // is nullable. Check that the types are the same by making a // nullable copy of both types and then comparing them. RexCall call = (RexCall) projExpr; if (call.getOperator() != SqlStdOperatorTable.CAST) { continue; } RexNode castOperand = call.getOperands().get(0); if (!(castOperand instanceof RexInputRef)) { continue; } RelDataTypeFactory typeFactory = rel.getCluster().getTypeFactory(); RelDataType castType = typeFactory.createTypeWithNullability(projExpr.getType(), true); RelDataType origType = typeFactory.createTypeWithNullability(castOperand.getType(), true); if (castType.equals(origType)) { childColumns.set(((RexInputRef) castOperand).getIndex()); } } else { // If the expression will not influence uniqueness of the // projection, then skip it. continue; } } // If no columns can affect uniqueness, then return unknown if (childColumns.cardinality() == 0) { return null; } return RelMetadataQuery.areColumnsUnique(rel.getChild(), childColumns, ignoreNulls); }
/** * Generates generic XML (sometimes called 'element-oriented XML'). Like this: * * <blockquote> * * <code> * <RelNode id="1" type="Join"><br/> * <Property name="condition">EMP.DEPTNO = * DEPT.DEPTNO</Property><br/> * <Inputs><br/> * <RelNode id="2" type="Project"><br/> * <Property name="expr1">x + * y</Property><br/> * <Property * name="expr2">45</Property><br/> * </RelNode><br/> * <RelNode id="3" type="TableAccess"><br/> * <Property * name="table">SALES.EMP</Property><br/> * </RelNode><br/> * </Inputs><br/> * </RelNode><br/> * </code> * * </blockquote> * * @param rel Relational expression * @param terms Names of the attributes of the plan * @param values Values of the attributes of the plan */ private void explainGeneric(RelNode rel, String[] terms, Object[] values) { RelNode[] inputs = rel.getInputs(); RexNode[] children = rel.getChildExps(); assert terms.length == (inputs.length + children.length + values.length) : "terms.length=" + terms.length + " inputs.length=" + inputs.length + " children.length=" + children.length + " values.length=" + values.length; String relType = rel.getRelTypeName(); xmlOutput.beginBeginTag("RelNode"); xmlOutput.attribute("type", relType); // xmlOutput.attribute("id", rel.getId() + ""); xmlOutput.endBeginTag("RelNode"); int j = 0; for (int i = 0; i < children.length; i++) { RexNode child = children[i]; xmlOutput.beginBeginTag("Property"); xmlOutput.attribute("name", terms[inputs.length + j++]); xmlOutput.endBeginTag("Property"); xmlOutput.cdata(child.toString()); xmlOutput.endTag("Property"); } for (int i = 0; i < values.length; i++) { Object value = values[i]; if (value != null) { xmlOutput.beginBeginTag("Property"); xmlOutput.attribute("name", terms[inputs.length + j++]); xmlOutput.endBeginTag("Property"); xmlOutput.cdata(value.toString()); xmlOutput.endTag("Property"); } } xmlOutput.beginTag("Inputs", null); level++; for (int i = 0; i < inputs.length; i++) { RelNode child = inputs[i]; child.explain(this); } level--; xmlOutput.endTag("Inputs"); xmlOutput.endTag("RelNode"); }
private void addResult(RexNode exp) { // Cast of literal can't be reduced, so skip those (otherwise we'd // go into an infinite loop as we add them back). if (exp.getKind() == RexKind.Cast) { RexCall cast = (RexCall) exp; RexNode operand = cast.getOperands()[0]; if (operand instanceof RexLiteral) { return; } } constExprs.add(exp); // In the case where the expression corresponds to a UDR argument, // we need to preserve casts. Note that this only applies to // the topmost argument, not expressions nested within the UDR // call. // // REVIEW zfong 6/13/08 - Are there other expressions where we // also need to preserve casts? if (parentCallTypeStack.isEmpty()) { addCasts.add(false); } else { addCasts.add( parentCallTypeStack.get(parentCallTypeStack.size() - 1) instanceof FarragoUserDefinedRoutine); } }
// override RexShuttle public RexNode visitCall(final RexCall call) { int i = reducibleExps.indexOf(call); if (i == -1) { return super.visitCall(call); } RexNode replacement = reducedValues.get(i); if (addCasts.get(i) && (replacement.getType() != call.getType())) { // Handle change from nullable to NOT NULL by claiming // that the result is still nullable, even though // we know it isn't. // // Also, we cannot reduce CAST('abc' AS VARCHAR(4)) to 'abc'. // If we make 'abc' of type VARCHAR(4), we may later encounter // the same expression in a ProjectRel's digest where it has // type VARCHAR(3), and that's wrong. replacement = rexBuilder.makeCast(call.getType(), replacement); } return replacement; }
/** * Determines if a projection is simple. * * @param calcRel CalcRel containing the projection * @param projOrdinals if the projection is simple, returns the ordinals of the projection inputs * @return rowtype corresponding to the projection, provided it is simple; otherwise null is * returned */ private RelDataType isProjectSimple(CalcRel calcRel, List<Integer> projOrdinals) { // Loop through projection expressions. If we find a non-simple // projection expression, simply return. RexProgram program = calcRel.getProgram(); List<RexLocalRef> projList = program.getProjectList(); int nProjExprs = projList.size(); RelDataType[] types = new RelDataType[nProjExprs]; String[] fieldNames = new String[nProjExprs]; RelDataTypeField[] projFields = calcRel.getRowType().getFields(); for (int i = 0; i < nProjExprs; i++) { RexNode projExpr = program.expandLocalRef(projList.get(i)); if (projExpr instanceof RexInputRef) { projOrdinals.add(((RexInputRef) projExpr).getIndex()); types[i] = projExpr.getType(); fieldNames[i] = projFields[i].getName(); continue; } else if (!(projExpr instanceof RexCall)) { return null; } RexCall rexCall = (RexCall) projExpr; if (rexCall.getOperator() != SqlStdOperatorTable.castFunc) { return null; } RexNode castOperand = rexCall.getOperands()[0]; if (!(castOperand instanceof RexInputRef)) { return null; } RelDataType castType = projExpr.getType(); RelDataType origType = castOperand.getType(); if (isCastSimple(origType, castType)) { projOrdinals.add(((RexInputRef) castOperand).getIndex()); types[i] = castType; fieldNames[i] = projFields[i].getName(); } else { return null; } } // return the rowtype corresponding to the output of the projection return calcRel.getCluster().getTypeFactory().createStructType(types, fieldNames); }
public void onMatch(RelOptRuleCall call) { FilterRel filter = (FilterRel) call.rels[0]; List<RexNode> expList = new ArrayList<RexNode>(Arrays.asList(filter.getChildExps())); RexNode newConditionExp; boolean reduced; if (reduceExpressions(filter, expList)) { assert (expList.size() == 1); newConditionExp = expList.get(0); reduced = true; } else { // No reduction, but let's still test the original // predicate to see if it was already a constant, // in which case we don't need any runtime decision // about filtering. newConditionExp = filter.getChildExps()[0]; reduced = false; } if (newConditionExp.isAlwaysTrue()) { call.transformTo(filter.getChild()); } else if ((newConditionExp instanceof RexLiteral) || RexUtil.isNullLiteral(newConditionExp, true)) { call.transformTo(new EmptyRel(filter.getCluster(), filter.getRowType())); } else if (reduced) { call.transformTo(CalcRel.createFilter(filter.getChild(), expList.get(0))); } else { if (newConditionExp instanceof RexCall) { RexCall rexCall = (RexCall) newConditionExp; boolean reverse = (rexCall.getOperator() == SqlStdOperatorTable.notOperator); if (reverse) { rexCall = (RexCall) rexCall.getOperands()[0]; } reduceNotNullableFilter(call, filter, rexCall, reverse); } return; } // New plan is absolutely better than old plan. call.getPlanner().setImportance(filter, 0.0); }
/** Variant of {@link #trimFields(RelNode, BitSet, Set)} for {@link FilterRel}. */ public TrimResult trimFields( FilterRel filter, BitSet fieldsUsed, Set<RelDataTypeField> extraFields) { final RelDataType rowType = filter.getRowType(); final int fieldCount = rowType.getFieldCount(); final RexNode conditionExpr = filter.getCondition(); final RelNode input = filter.getChild(); // We use the fields used by the consumer, plus any fields used in the // filter. BitSet inputFieldsUsed = (BitSet) fieldsUsed.clone(); final Set<RelDataTypeField> inputExtraFields = new LinkedHashSet<RelDataTypeField>(extraFields); RelOptUtil.InputFinder inputFinder = new RelOptUtil.InputFinder(inputFieldsUsed, inputExtraFields); conditionExpr.accept(inputFinder); // Create input with trimmed columns. TrimResult trimResult = trimChild(filter, input, inputFieldsUsed, inputExtraFields); RelNode newInput = trimResult.left; final Mapping inputMapping = trimResult.right; // If the input is unchanged, and we need to project all columns, // there's nothing we can do. if (newInput == input && fieldsUsed.cardinality() == fieldCount) { return new TrimResult(filter, Mappings.createIdentity(fieldCount)); } // Build new project expressions, and populate the mapping. final RexVisitor<RexNode> shuttle = new RexPermuteInputsShuttle(inputMapping, newInput); RexNode newConditionExpr = conditionExpr.accept(shuttle); final FilterRel newFilter = new FilterRel(filter.getCluster(), newInput, newConditionExpr); assert newFilter.getClass() == filter.getClass(); // The result has the same mapping as the input gave us. Sometimes we // return fields that the consumer didn't ask for, because the filter // needs them for its condition. return new TrimResult(newFilter, inputMapping); }
public void analyze(RexNode exp) { assert (stack.isEmpty()); exp.accept(this); // Deal with top of stack assert (stack.size() == 1); assert (parentCallTypeStack.isEmpty()); Constancy rootConstancy = stack.get(0); if (rootConstancy == Constancy.REDUCIBLE_CONSTANT) { // The entire subtree was constant, so add it to the result. addResult(exp); } stack.clear(); }
/** * Shifts a filter originating from the right child of the JoinRel to the right, to reflect the * filter now being applied on the resulting MultiJoinRel. * * @param joinRel the original JoinRel * @param left the left child of the JoinRel * @param right the right child of the JoinRel * @param rightFilter the filter originating from the right child * @return the adjusted right filter */ private RexNode shiftRightFilter( JoinRel joinRel, RelNode left, MultiJoinRel right, RexNode rightFilter) { if (rightFilter == null) { return null; } int nFieldsOnLeft = left.getRowType().getFields().length; int nFieldsOnRight = right.getRowType().getFields().length; int[] adjustments = new int[nFieldsOnRight]; for (int i = 0; i < nFieldsOnRight; i++) { adjustments[i] = nFieldsOnLeft; } rightFilter = rightFilter.accept( new RelOptUtil.RexInputConverter( joinRel.getCluster().getRexBuilder(), right.getRowType().getFields(), joinRel.getRowType().getFields(), adjustments)); return rightFilter; }
/** * Adds on to the existing join condition reference counts the references from the new join * condition. * * @param multiJoinInputs inputs into the new MultiJoinRel * @param nTotalFields total number of fields in the MultiJoinRel * @param joinCondition the new join condition * @param origJoinFieldRefCounts existing join condition reference counts * @param newJoinFieldRefCountsMap map containing the new join condition reference counts, indexed * by input # */ private void addOnJoinFieldRefCounts( RelNode[] multiJoinInputs, int nTotalFields, RexNode joinCondition, List<int[]> origJoinFieldRefCounts, Map<Integer, int[]> newJoinFieldRefCountsMap) { // count the input references in the join condition int[] joinCondRefCounts = new int[nTotalFields]; joinCondition.accept(new InputReferenceCounter(joinCondRefCounts)); // first, make a copy of the ref counters int nInputs = multiJoinInputs.length; int currInput = 0; for (int[] origRefCounts : origJoinFieldRefCounts) { newJoinFieldRefCountsMap.put(currInput, (int[]) origRefCounts.clone()); currInput++; } // add on to the counts for each input into the MultiJoinRel the // reference counts computed for the current join condition currInput = -1; int startField = 0; int nFields = 0; for (int i = 0; i < nTotalFields; i++) { if (joinCondRefCounts[i] == 0) { continue; } while (i >= (startField + nFields)) { startField += nFields; currInput++; assert (currInput < nInputs); nFields = multiJoinInputs[currInput].getRowType().getFieldCount(); } int[] refCounts = newJoinFieldRefCountsMap.get(currInput); refCounts[i - startField] += joinCondRefCounts[i]; } }
/** * Analyzes a rex predicate. * * @param rexPredicate predicate to be analyzed * @return corresponding bound sarg expression, or null if analysis failed */ public SargBinding analyze(RexNode rexPredicate) { NodeVisitor visitor = new NodeVisitor(); // Initialize analysis state. exprStack = new ArrayList<SargExpr>(); failed = false; boundInputRef = null; clearLeaf(); // Walk the predicate. rexPredicate.accept(visitor); if (boundInputRef == null) { // No variable references at all, so not sargable. failed = true; } if (exprStack.isEmpty()) { failed = true; } if (failed) { return null; } // well-formedness assumption assert (exprStack.size() == 1); SargExpr expr = exprStack.get(0); if (!testDynamicParamSupport(expr)) { failed = true; return null; } return new SargBinding(expr, boundInputRef); }
@Override public TupleFilter visitCall(RexCall call) { TupleFilter filter = null; SqlOperator op = call.getOperator(); switch (op.getKind()) { case AND: filter = new LogicalTupleFilter(FilterOperatorEnum.AND); break; case OR: filter = new LogicalTupleFilter(FilterOperatorEnum.OR); break; case NOT: filter = new LogicalTupleFilter(FilterOperatorEnum.NOT); break; case EQUALS: filter = new CompareTupleFilter(FilterOperatorEnum.EQ); break; case GREATER_THAN: filter = new CompareTupleFilter(FilterOperatorEnum.GT); break; case LESS_THAN: filter = new CompareTupleFilter(FilterOperatorEnum.LT); break; case GREATER_THAN_OR_EQUAL: filter = new CompareTupleFilter(FilterOperatorEnum.GTE); break; case LESS_THAN_OR_EQUAL: filter = new CompareTupleFilter(FilterOperatorEnum.LTE); break; case NOT_EQUALS: filter = new CompareTupleFilter(FilterOperatorEnum.NEQ); break; case IS_NULL: filter = new CompareTupleFilter(FilterOperatorEnum.ISNULL); break; case IS_NOT_NULL: filter = new CompareTupleFilter(FilterOperatorEnum.ISNOTNULL); break; case CAST: case REINTERPRET: // NOTE: use child directly break; case CASE: filter = new CaseTupleFilter(); break; case OTHER: if (op.getName().equalsIgnoreCase("extract_date")) { filter = new ExtractTupleFilter(FilterOperatorEnum.EXTRACT); } else { throw new UnsupportedOperationException(op.getName()); } break; default: throw new UnsupportedOperationException(op.getName()); } for (RexNode operand : call.operands) { TupleFilter childFilter = operand.accept(this); if (filter == null) { filter = childFilter; } else { filter.addChild(childFilter); } } if (op.getKind() == SqlKind.OR) { CompareTupleFilter inFilter = mergeToInClause(filter); if (inFilter != null) { filter = inFilter; } } return filter; }
protected void executeImpl() throws Exception { SqlCall call = (SqlCall) subq; SqlSelect select = (SqlSelect) call.getOperands()[0]; // Convert the SqlNode tree to a RelNode tree; we need to do this // here so the RelNode tree is associated with the new preparing // stmt. FarragoPreparingStmt preparingStmt = (FarragoPreparingStmt) getPreparingStmt(); SqlValidator validator = preparingStmt.getSqlValidator(); SqlToRelConverter sqlConverter = preparingStmt.getSqlToRelConverter(validator, preparingStmt); preparingStmt.setParentStmt(FennelRelUtil.getPreparingStmt(parentConverter.getCluster())); // Add to the new converter any subqueries that have already been // converted by the parent so we can avoid re-executing them sqlConverter.addConvertedNonCorrSubqs(parentConverter.getMapConvertedNonCorrSubqs()); RelNode plan = sqlConverter.convertQuery(select, true, true); // The subquery cannot have dynamic parameters if (sqlConverter.getDynamicParamCount() > 0) { failed = true; return; } List<RexNode> exprs = new ArrayList<RexNode>(); RelDataType resultType = null; if (!isExists) { // Non-EXISTS subqueries need to be converted to single-value // subqueries plan = sqlConverter.convertToSingleValueSubq(select, plan); // Create a dummy expression to store the type of the result. // When setting the type, derive the type based on what a // scalar subquery should return and create the type from the // type factory of the parent query. resultType = call.getOperator().deriveType(validator, validator.getFromScope(select), call); resultType = rexBuilder.getTypeFactory().copyType(resultType); exprs.add(rexBuilder.makeInputRef(resultType, 0)); } plan = sqlConverter.decorrelate(select, plan); // If the subquery is part of an EXPLAIN PLAN statement, don't // execute the subquery, but instead just return a dynamic parameter // as a placeholder for the subquery result. Otherwise, execute // the query to produce the constant expression. Cast the expression // as needed so the type matches the expected result type. RexNode constExpr; if (isExplain) { if (isExists) { resultType = rexBuilder.getTypeFactory().createSqlType(SqlTypeName.BOOLEAN); } constExpr = rexBuilder.makeDynamicParam( resultType, parentConverter.getDynamicParamCountInExplain(true)); results.add(constExpr); } else { executePlan(plan, exprs, isExists, false); if (!failed && !isExists) { constExpr = results.get(0); if (constExpr.getType() != resultType) { constExpr = rexBuilder.makeCast(resultType, constExpr); results.set(0, constExpr); } } } }
/** Variant of {@link #trimFields(RelNode, BitSet, Set)} for {@link JoinRel}. */ public TrimResult trimFields(JoinRel join, BitSet fieldsUsed, Set<RelDataTypeField> extraFields) { final RelDataType rowType = join.getRowType(); final int fieldCount = rowType.getFieldCount(); final RexNode conditionExpr = join.getCondition(); final int systemFieldCount = join.getSystemFieldList().size(); // Add in fields used in the condition. BitSet fieldsUsedPlus = (BitSet) fieldsUsed.clone(); final Set<RelDataTypeField> combinedInputExtraFields = new LinkedHashSet<RelDataTypeField>(extraFields); RelOptUtil.InputFinder inputFinder = new RelOptUtil.InputFinder(fieldsUsedPlus, combinedInputExtraFields); conditionExpr.accept(inputFinder); // If no system fields are used, we can remove them. int systemFieldUsedCount = 0; for (int i = 0; i < systemFieldCount; ++i) { if (fieldsUsed.get(i)) { ++systemFieldUsedCount; } } final int newSystemFieldCount; if (systemFieldUsedCount == 0) { newSystemFieldCount = 0; } else { newSystemFieldCount = systemFieldCount; } int offset = systemFieldCount; int changeCount = 0; int newFieldCount = newSystemFieldCount; List<RelNode> newInputs = new ArrayList<RelNode>(2); List<Mapping> inputMappings = new ArrayList<Mapping>(); List<Integer> inputExtraFieldCounts = new ArrayList<Integer>(); for (RelNode input : join.getInputs()) { final RelDataType inputRowType = input.getRowType(); final int inputFieldCount = inputRowType.getFieldCount(); // Compute required mapping. BitSet inputFieldsUsed = new BitSet(inputFieldCount); for (int bit : Util.toIter(fieldsUsedPlus)) { if (bit >= offset && bit < offset + inputFieldCount) { inputFieldsUsed.set(bit - offset); } } // If there are system fields, we automatically use the // corresponding field in each input. if (newSystemFieldCount > 0) { // calling with newSystemFieldCount == 0 should be safe but hits // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6222207 inputFieldsUsed.set(0, newSystemFieldCount); } // FIXME: We ought to collect extra fields for each input // individually. For now, we assume that just one input has // on-demand fields. Set<RelDataTypeField> inputExtraFields = input.getRowType().getField("_extra") == null ? Collections.<RelDataTypeField>emptySet() : combinedInputExtraFields; inputExtraFieldCounts.add(inputExtraFields.size()); TrimResult trimResult = trimChild(join, input, inputFieldsUsed, inputExtraFields); newInputs.add(trimResult.left); if (trimResult.left != input) { ++changeCount; } final Mapping inputMapping = trimResult.right; inputMappings.add(inputMapping); // Move offset to point to start of next input. offset += inputFieldCount; newFieldCount += inputMapping.getTargetCount() + inputExtraFields.size(); } Mapping mapping = Mappings.create(MappingType.InverseSurjection, fieldCount, newFieldCount); for (int i = 0; i < newSystemFieldCount; ++i) { mapping.set(i, i); } offset = systemFieldCount; int newOffset = newSystemFieldCount; for (int i = 0; i < inputMappings.size(); i++) { Mapping inputMapping = inputMappings.get(i); for (IntPair pair : inputMapping) { mapping.set(pair.source + offset, pair.target + newOffset); } offset += inputMapping.getSourceCount(); newOffset += inputMapping.getTargetCount() + inputExtraFieldCounts.get(i); } if (changeCount == 0 && mapping.isIdentity()) { return new TrimResult(join, Mappings.createIdentity(fieldCount)); } // Build new join. final RexVisitor<RexNode> shuttle = new RexPermuteInputsShuttle(mapping, newInputs.get(0), newInputs.get(1)); RexNode newConditionExpr = conditionExpr.accept(shuttle); final JoinRel newJoin = join.copy(join.getTraitSet(), newConditionExpr, newInputs.get(0), newInputs.get(1)); return new TrimResult(newJoin, mapping); }