/* * JBoss, Home of Professional Open Source. * See the COPYRIGHT.txt file distributed with this work for information * regarding copyright ownership. Some portions may be licensed * to Red Hat, Inc. under one or more contributor license agreements. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ package org.teiid.query.optimizer.relational.rules; import java.util.Arrays; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import org.teiid.api.exception.query.ExpressionEvaluationException; import org.teiid.api.exception.query.QueryMetadataException; import org.teiid.api.exception.query.QueryPlannerException; import org.teiid.common.buffer.BlockedException; import org.teiid.core.TeiidComponentException; import org.teiid.core.TeiidException; import org.teiid.core.TeiidRuntimeException; import org.teiid.core.types.DataTypeManager; import org.teiid.query.QueryPlugin; import org.teiid.query.analysis.AnalysisRecord; import org.teiid.query.eval.Evaluator; import org.teiid.query.function.FunctionLibrary; import org.teiid.query.metadata.QueryMetadataInterface; import org.teiid.query.optimizer.capabilities.CapabilitiesFinder; import org.teiid.query.optimizer.relational.OptimizerRule; import org.teiid.query.optimizer.relational.RelationalPlanner; import org.teiid.query.optimizer.relational.RuleStack; import org.teiid.query.optimizer.relational.plantree.NodeConstants; import org.teiid.query.optimizer.relational.plantree.NodeConstants.Info; import org.teiid.query.optimizer.relational.plantree.NodeEditor; import org.teiid.query.optimizer.relational.plantree.NodeFactory; import org.teiid.query.optimizer.relational.plantree.PlanNode; import org.teiid.query.sql.lang.CompareCriteria; import org.teiid.query.sql.lang.Criteria; import org.teiid.query.sql.lang.JoinType; import org.teiid.query.sql.lang.OrderBy; import org.teiid.query.sql.lang.OrderByItem; import org.teiid.query.sql.lang.SetQuery; import org.teiid.query.sql.symbol.Constant; import org.teiid.query.sql.symbol.ElementSymbol; import org.teiid.query.sql.symbol.Expression; import org.teiid.query.sql.symbol.Function; import org.teiid.query.sql.symbol.GroupSymbol; import org.teiid.query.sql.symbol.SearchedCaseExpression; import org.teiid.query.sql.util.SymbolMap; import org.teiid.query.sql.visitor.EvaluatableVisitor; import org.teiid.query.util.CommandContext; import org.teiid.translator.SourceSystemFunctions; /** * Pushes limit nodes to their lowest points. This rule should only be run once. Should be run after all access nodes have been raised */ public class RulePushLimit implements OptimizerRule { /** * @see org.teiid.query.optimizer.relational.OptimizerRule#execute(org.teiid.query.optimizer.relational.plantree.PlanNode, org.teiid.query.metadata.QueryMetadataInterface, org.teiid.query.optimizer.capabilities.CapabilitiesFinder, org.teiid.query.optimizer.relational.RuleStack, org.teiid.query.analysis.AnalysisRecord, org.teiid.query.util.CommandContext) */ public PlanNode execute(PlanNode plan, QueryMetadataInterface metadata, CapabilitiesFinder capabilitiesFinder, RuleStack rules, AnalysisRecord analysisRecord, CommandContext context) throws QueryPlannerException, QueryMetadataException, TeiidComponentException { List<PlanNode> limitNodes = NodeEditor.findAllNodes(plan, NodeConstants.Types.TUPLE_LIMIT, NodeConstants.Types.ACCESS); boolean pushRaiseNull = false; PlanNode[] rootHolder = new PlanNode[] {plan}; while (!limitNodes.isEmpty()) { PlanNode limitNode = limitNodes.get(0); Expression limit = (Expression)limitNode.getProperty(NodeConstants.Info.MAX_TUPLE_LIMIT); if (limit instanceof Constant && new Integer(0).equals(((Constant)limit).getValue())) { PlanNode childProject = NodeEditor.findNodePreOrder(limitNode, NodeConstants.Types.PROJECT); if (childProject != null && childProject.getProperty(NodeConstants.Info.INTO_GROUP) == null) { limitNodes.removeAll(NodeEditor.findAllNodes(limitNode.getFirstChild(), NodeConstants.Types.TUPLE_LIMIT, NodeConstants.Types.ACCESS)); FrameUtil.replaceWithNullNode(limitNode.getFirstChild()); PlanNode projectNode = NodeFactory.getNewNode(NodeConstants.Types.PROJECT); RelationalPlanner.createProjectNode((List<? extends Expression>) childProject.getProperty(NodeConstants.Info.PROJECT_COLS)); limitNode.getFirstChild().addAsParent(projectNode); projectNode.setProperty(NodeConstants.Info.OUTPUT_COLS, projectNode.getProperty(NodeConstants.Info.PROJECT_COLS)); pushRaiseNull = true; limitNodes.remove(limitNode); continue; } } while (canPushLimit(rootHolder, limitNode, limitNodes, metadata, capabilitiesFinder, analysisRecord, context)) { rootHolder[0] = RuleRaiseAccess.performRaise(rootHolder[0], limitNode.getFirstChild(), limitNode); //makes this rule safe to run after the final rule assign output elements limitNode.setProperty(Info.OUTPUT_COLS, limitNode.getFirstChild().getProperty(Info.OUTPUT_COLS)); } limitNodes.remove(limitNode); if (limitNode.hasBooleanProperty(Info.IS_COPIED)) { limitNode.getParent().replaceChild(limitNode, limitNode.getFirstChild()); } } if (pushRaiseNull) { rules.push(RuleConstants.RAISE_NULL); } return rootHolder[0]; } private boolean canPushLimit(PlanNode[] rootNode, PlanNode limitNode, List<PlanNode> limitNodes, QueryMetadataInterface metadata, CapabilitiesFinder capFinder, AnalysisRecord record, CommandContext context) throws QueryMetadataException, TeiidComponentException, QueryPlannerException { PlanNode child = limitNode.getFirstChild(); if (child == null || child.getChildCount() == 0) { return false; } Expression parentLimit = (Expression)limitNode.getProperty(NodeConstants.Info.MAX_TUPLE_LIMIT); Expression parentOffset = (Expression)limitNode.getProperty(NodeConstants.Info.OFFSET_TUPLE_COUNT); switch (child.getType()) { case NodeConstants.Types.TUPLE_LIMIT: { //combine the limits Expression childLimit = (Expression)child.getProperty(NodeConstants.Info.MAX_TUPLE_LIMIT); Expression childOffset = (Expression)child.getProperty(NodeConstants.Info.OFFSET_TUPLE_COUNT); combineLimits(limitNode, metadata, parentLimit, parentOffset, childLimit, childOffset); if (child.hasBooleanProperty(Info.IS_NON_STRICT)) { limitNode.setProperty(Info.IS_NON_STRICT, true); } NodeEditor.removeChildNode(limitNode, child); limitNodes.remove(child); return canPushLimit(rootNode, limitNode, limitNodes, metadata, capFinder, record, context); } case NodeConstants.Types.SET_OP: { if (!canPushToBranches(limitNode, child)) { return false; } //distribute the limit List<PlanNode> grandChildren = new LinkedList<PlanNode>(child.getChildren()); for (PlanNode grandChild : grandChildren) { addBranchLimit(limitNode, limitNodes, metadata, parentLimit, parentOffset, grandChild); } return false; } case NodeConstants.Types.JOIN: if (parentLimit == null) { return false; } JoinType joinType = (JoinType)child.getProperty(Info.JOIN_TYPE); boolean pushLeft = false; boolean pushRight = false; if (joinType == JoinType.JOIN_CROSS) { pushLeft = true; pushRight = true; } else if (joinType == JoinType.JOIN_LEFT_OUTER || joinType == JoinType.JOIN_FULL_OUTER) { //we're allowed to do this based upon two conditions //1 - we're not going to further change the join type/structure //2 - outer results will be produced using the left product first pushLeft = true; } if (pushLeft && !FrameUtil.findJoinSourceNode(child.getLastChild()).hasProperty(NodeConstants.Info.CORRELATED_REFERENCES)) { PlanNode newLimit = newLimit(limitNode); newLimit.setProperty(NodeConstants.Info.MAX_TUPLE_LIMIT, op(SourceSystemFunctions.ADD_OP, parentLimit, parentOffset, metadata.getFunctionLibrary())); child.getFirstChild().addAsParent(newLimit); newLimit.setProperty(NodeConstants.Info.OUTPUT_COLS, newLimit.getFirstChild().getProperty(NodeConstants.Info.OUTPUT_COLS)); limitNodes.add(newLimit); } if (pushRight) { PlanNode newLimit = newLimit(limitNode); newLimit.setProperty(NodeConstants.Info.MAX_TUPLE_LIMIT, op(SourceSystemFunctions.ADD_OP, parentLimit, parentOffset, metadata.getFunctionLibrary())); child.getLastChild().addAsParent(newLimit); newLimit.setProperty(NodeConstants.Info.OUTPUT_COLS, newLimit.getFirstChild().getProperty(NodeConstants.Info.OUTPUT_COLS)); limitNodes.add(newLimit); } return false; case NodeConstants.Types.ACCESS: { raiseAccessOverLimit(rootNode[0], child, metadata, capFinder, limitNode, record); return false; } case NodeConstants.Types.PROJECT: { return child.getProperty(NodeConstants.Info.INTO_GROUP) == null && !child.hasProperty(Info.HAS_WINDOW_FUNCTIONS); } case NodeConstants.Types.SOURCE: { return canPushThroughView(child); } case NodeConstants.Types.SELECT: case NodeConstants.Types.DUP_REMOVE: return limitNode.hasBooleanProperty(Info.IS_NON_STRICT); case NodeConstants.Types.SORT: switch (child.getFirstChild().getType()) { case NodeConstants.Types.SOURCE: { if (canPushThroughView(child.getFirstChild())) { PlanNode sourceNode = child.getFirstChild(); NodeEditor.removeChildNode(limitNode, child); NodeEditor.removeChildNode(limitNode.getParent(), limitNode); limitNode.setProperty(NodeConstants.Info.OUTPUT_COLS, sourceNode.getFirstChild().getProperty(NodeConstants.Info.OUTPUT_COLS)); child.setProperty(NodeConstants.Info.OUTPUT_COLS, sourceNode.getFirstChild().getProperty(NodeConstants.Info.OUTPUT_COLS)); //project the order through the source - which needs to preserve the aliasing HashMap<ElementSymbol, Expression> symbolMap = new HashMap<ElementSymbol, Expression>(); List<ElementSymbol> virtual = ((SymbolMap)sourceNode.getProperty(NodeConstants.Info.SYMBOL_MAP)).getKeys(); List<Expression> projected = (List<Expression>) NodeEditor.findNodePreOrder(sourceNode, NodeConstants.Types.PROJECT).getProperty(Info.PROJECT_COLS); for (int i = 0; i < virtual.size(); i++) { symbolMap.put(virtual.get(i), projected.get(i)); } //map the expression directly to avoid issues with naming logic in the general node conversion OrderBy orderBy = (OrderBy) child.getProperty(Info.SORT_ORDER); for (OrderByItem item : orderBy.getOrderByItems()) { Expression ex = symbolMap.get(item.getSymbol()); if (ex != null) { item.setSymbol(ex); item.setExpressionPosition(projected.indexOf(ex)); } } FrameUtil.convertNode(child, sourceNode.getGroups().iterator().next(), null, symbolMap, metadata, false); sourceNode.getFirstChild().addAsParent(child); child.addAsParent(limitNode); //push again limitNodes.add(limitNode); return false; } } case NodeConstants.Types.SET_OP: { PlanNode setOp = child.getFirstChild(); if (!canPushToBranches(limitNode, setOp)) { return false; } OrderBy parentOrderBy = (OrderBy) child.getProperty(NodeConstants.Info.SORT_ORDER); distributeLimit(limitNode, setOp, parentOrderBy, metadata, limitNodes, parentLimit, parentOffset, capFinder, context); break; } case NodeConstants.Types.JOIN: { if (parentLimit == null) { return false; } PlanNode join = child.getFirstChild(); JoinType jt = (JoinType)join.getProperty(NodeConstants.Info.JOIN_TYPE); if (!jt.isOuter()) { return false; } if ((jt == JoinType.JOIN_FULL_OUTER || jt == JoinType.JOIN_LEFT_OUTER) && join.getFirstChild().getGroups().containsAll(child.getGroups()) && !FrameUtil.findJoinSourceNode(join.getLastChild()).hasProperty(NodeConstants.Info.CORRELATED_REFERENCES)) { pushOrderByAndLimit(limitNode, limitNodes, metadata, capFinder, context, child, parentLimit, parentOffset, join.getFirstChild()); } else if (jt == JoinType.JOIN_FULL_OUTER && join.getLastChild().getGroups().containsAll(child.getGroups())) { pushOrderByAndLimit(limitNode, limitNodes, metadata, capFinder, context, child, parentLimit, parentOffset, join.getLastChild()); } break; } case NodeConstants.Types.PROJECT: { rootNode[0] = RulePlanSorts.checkForProjectOptimization(child, rootNode[0], metadata, capFinder, record, context); if (child.getFirstChild().getType() != NodeConstants.Types.PROJECT && NodeEditor.findParent(child, NodeConstants.Types.ACCESS) == null) { return canPushLimit(rootNode, limitNode, limitNodes, metadata, capFinder, record, context); } break; } case NodeConstants.Types.ACCESS: { if (RuleRaiseAccess.canRaiseOverSort(child.getFirstChild(), metadata, capFinder, child, null, false, context)) { NodeEditor.removeChildNode(limitNode, child); limitNode.getFirstChild().getFirstChild().addAsParent(child); limitNodes.add(limitNode); //try to keep pushing return false; } } } return false; default: { return false; } } } private boolean canPushThroughView(PlanNode child) { if (child.getChildCount() == 0) { return false; //not a view } GroupSymbol virtualGroup = child.getGroups().iterator().next(); if (virtualGroup.isProcedure()) { return false; } if (FrameUtil.isProcedure(child.getFirstChild())) { return false; } if (child.hasProperty(Info.TABLE_FUNCTION)) { return false; } return true; } private void pushOrderByAndLimit(PlanNode limitNode, List<PlanNode> limitNodes, QueryMetadataInterface metadata, CapabilitiesFinder capFinder, CommandContext context, PlanNode child, Expression parentLimit, Expression parentOffset, PlanNode branch) throws QueryMetadataException, TeiidComponentException { //push both the limit and order by OrderBy parentOrderBy = (OrderBy) child.getProperty(NodeConstants.Info.SORT_ORDER); PlanNode newSort = NodeFactory.getNewNode(NodeConstants.Types.SORT); OrderBy newOrderBy = parentOrderBy.clone(); newSort.setProperty(Info.SORT_ORDER, newOrderBy); newSort.addGroups(child.getGroups()); newSort.setProperty(NodeConstants.Info.OUTPUT_COLS, branch.getProperty(NodeConstants.Info.OUTPUT_COLS)); branch.addAsParent(newSort); addBranchLimit(limitNode, limitNodes, metadata, parentLimit, parentOffset, newSort); if (limitNode.hasBooleanProperty(Info.IS_PUSHED)) { //remove the intermediate ordering/limit NodeEditor.removeChildNode(limitNode, limitNode.getFirstChild()); NodeEditor.removeChildNode(limitNode.getParent(), limitNode); } } private void addBranchLimit(PlanNode limitNode, List<PlanNode> limitNodes, QueryMetadataInterface metadata, Expression parentLimit, Expression parentOffset, PlanNode grandChild) { PlanNode newLimit = newLimit(limitNode); newLimit.setProperty(NodeConstants.Info.MAX_TUPLE_LIMIT, op(SourceSystemFunctions.ADD_OP, parentLimit, parentOffset, metadata.getFunctionLibrary())); grandChild.addAsParent(newLimit); newLimit.setProperty(NodeConstants.Info.OUTPUT_COLS, newLimit.getFirstChild().getProperty(NodeConstants.Info.OUTPUT_COLS)); if (NodeEditor.findParent(newLimit, NodeConstants.Types.ACCESS) == null) { limitNodes.add(newLimit); } if (grandChild.getType() == NodeConstants.Types.SET_OP) { newLimit.setProperty(Info.IS_COPIED, true); } newLimit.setProperty(Info.IS_PUSHED, true); } /** * Push the limit and order by to each union branch * TODO: check if the top limit is smaller and implement sorted sublist processing, rather than performing a full resort * @param context */ private void distributeLimit(PlanNode limitNode, PlanNode setOp, OrderBy parentOrderBy, QueryMetadataInterface metadata, List<PlanNode> limitNodes, Expression parentLimit, Expression parentOffset, CapabilitiesFinder capFinder, CommandContext context) throws QueryMetadataException, TeiidComponentException { outer: for (PlanNode branch : setOp.getChildren()) { PlanNode branchSort = NodeEditor.findNodePreOrder(branch, NodeConstants.Types.SORT, NodeConstants.Types.SET_OP | NodeConstants.Types.SOURCE); if (branchSort != null) { //implies there is a limit OrderBy orderBy = (OrderBy) branchSort.getProperty(NodeConstants.Info.SORT_ORDER); //can only proceed if order by matches if (parentOrderBy.getOrderByItems().size() > orderBy.getOrderByItems().size()) { continue; } List<OrderByItem> parentkeys = parentOrderBy.getOrderByItems(); List<OrderByItem> keys = orderBy.getOrderByItems(); for (int i = 0; i < parentkeys.size(); i++) { int pos1 = parentkeys.get(i).getExpressionPosition(); int pos2 = keys.get(i).getExpressionPosition(); if (pos1 == -1 || pos2 == -1 || pos1 != pos2) { continue outer; } } addBranchLimit(limitNode, limitNodes, metadata, parentLimit, parentOffset, branch); } else { if (branch.getType() == NodeConstants.Types.SET_OP && canPushToBranches(limitNode, branch)) { //go to the children distributeLimit(limitNode, branch, parentOrderBy, metadata, limitNodes, parentLimit, parentOffset, capFinder, context); continue; } PlanNode newSort = NodeFactory.getNewNode(NodeConstants.Types.SORT); //push both the limit and order by List<OrderByItem> parentkeys = parentOrderBy.getOrderByItems(); List<Expression> cols = (List<Expression>) NodeEditor.findNodePreOrder(branch, NodeConstants.Types.PROJECT).getProperty(NodeConstants.Info.PROJECT_COLS); OrderBy newOrderBy = new OrderBy(); for (int i = 0; i < parentkeys.size(); i++) { OrderByItem item = parentkeys.get(i).clone(); if (item.getExpressionPosition() == -1) { continue outer; } Expression ex = cols.get(item.getExpressionPosition()); item.setSymbol((Expression) ex.clone()); newOrderBy.getOrderByItems().add(item); } newSort.setProperty(Info.SORT_ORDER, newOrderBy); PlanNode childLimit = NodeEditor.findNodePreOrder(branch, NodeConstants.Types.TUPLE_LIMIT, NodeConstants.Types.SET_OP | NodeConstants.Types.SOURCE); if (childLimit != null) { PlanNode parentAccess = NodeEditor.findParent(childLimit, NodeConstants.Types.ACCESS, NodeConstants.Types.SET_OP); if (parentAccess != null) { //if there is a parent access, we need to handle pushing the sort boolean removedLimit = false; if (parentAccess.getFirstChild() == childLimit) { parentAccess.removeChild(childLimit); parentAccess.addFirstChild(childLimit.getFirstChild()); removedLimit = true; } boolean canRaise = RuleRaiseAccess.canRaiseOverSort(parentAccess, metadata, capFinder, newSort, null, false, context); if (removedLimit) { childLimit.addFirstChild(parentAccess.getFirstChild()); parentAccess.addFirstChild(childLimit); } if (canRaise) { //put under the limit //TODO: check to make sure that we're narrowing the limit //we won't know this in all cases since it could be parameterized childLimit.getFirstChild().addAsParent(newSort); } else { continue outer; //TODO - once we support sorted sublist processing, then we'll want to push //put over the access node //parentAccess.addAsParent(newSort); //branch = newSort; } } else { continue outer; //TODO - once we support sorted sublist processing, then we'll want to push //branch.addAsParent(newSort); //branch = newSort; } } else { if (branch.getType() == NodeConstants.Types.ACCESS && RuleRaiseAccess.canRaiseOverSort(branch, metadata, capFinder, newSort, null, false, context)) { branch.getFirstChild().addAsParent(newSort); } else { //TODO: if the limit is too large we shouldn't add it in here branch.addAsParent(newSort); branch = newSort; } } newSort.setProperty(NodeConstants.Info.OUTPUT_COLS, newSort.getFirstChild().getProperty(NodeConstants.Info.OUTPUT_COLS)); addBranchLimit(limitNode, limitNodes, metadata, parentLimit, parentOffset, branch); } } } private boolean canPushToBranches(PlanNode limitNode, PlanNode child) { if (!limitNode.hasProperty(Info.MAX_TUPLE_LIMIT)) { return false; } if (!SetQuery.Operation.UNION.equals(child.getProperty(NodeConstants.Info.SET_OPERATION))) { return false; } if (!child.hasBooleanProperty(NodeConstants.Info.USE_ALL) && !limitNode.hasBooleanProperty(Info.IS_NON_STRICT)) { return false; } return true; } private static PlanNode newLimit(PlanNode limitNode) { PlanNode newLimit = NodeFactory.getNewNode(NodeConstants.Types.TUPLE_LIMIT); if (limitNode.hasBooleanProperty(Info.IS_NON_STRICT)) { newLimit.setProperty(Info.IS_NON_STRICT, Boolean.TRUE); } return newLimit; } static void combineLimits(PlanNode limitNode, QueryMetadataInterface metadata, Expression parentLimit, Expression parentOffset, Expression childLimit, Expression childOffset) { Expression minLimit = null; Expression offSet = null; if (childLimit == null) { minLimit = parentLimit; offSet = op(SourceSystemFunctions.ADD_OP, childOffset, parentOffset, metadata.getFunctionLibrary()); } else { minLimit = getMinValue(parentLimit, op(SourceSystemFunctions.SUBTRACT_OP, childLimit, parentOffset, metadata.getFunctionLibrary())); offSet = childOffset; if (offSet == null) { offSet = parentOffset; } } limitNode.setProperty(NodeConstants.Info.MAX_TUPLE_LIMIT, minLimit); limitNode.setProperty(NodeConstants.Info.OFFSET_TUPLE_COUNT, offSet); } static PlanNode raiseAccessOverLimit(PlanNode rootNode, PlanNode accessNode, QueryMetadataInterface metadata, CapabilitiesFinder capFinder, PlanNode parentNode, AnalysisRecord analysisRecord) throws QueryMetadataException, TeiidComponentException { Object modelID = RuleRaiseAccess.getModelIDFromAccess(accessNode, metadata); if (modelID == null) { return null; } Expression limit = (Expression)parentNode.getProperty(NodeConstants.Info.MAX_TUPLE_LIMIT); if (limit != null && !CapabilitiesUtil.supportsRowLimit(modelID, metadata, capFinder)) { parentNode.recordDebugAnnotation("limit not supported by source", modelID, "limit node not pushed", analysisRecord, metadata); //$NON-NLS-1$ //$NON-NLS-2$ return null; } boolean multiSource = accessNode.hasBooleanProperty(Info.IS_MULTI_SOURCE); Expression offset = (Expression)parentNode.getProperty(NodeConstants.Info.OFFSET_TUPLE_COUNT); if (multiSource || (offset != null && !CapabilitiesUtil.supportsRowOffset(modelID, metadata, capFinder))) { if (limit != null) { if (!multiSource) { parentNode.setProperty(NodeConstants.Info.MAX_TUPLE_LIMIT, null); } PlanNode pushedLimit = newLimit(parentNode); // since we're pushing underneath the offset, we want enough rows to satisfy both the limit and the row offset pushedLimit.setProperty(NodeConstants.Info.MAX_TUPLE_LIMIT, op(SourceSystemFunctions.ADD_OP, limit, offset, metadata.getFunctionLibrary())); if (accessNode.getChildCount() == 0) { accessNode.addFirstChild(pushedLimit); } else { accessNode.getFirstChild().addAsParent(pushedLimit); } pushedLimit.setProperty(NodeConstants.Info.OUTPUT_COLS, pushedLimit.getFirstChild().getProperty(NodeConstants.Info.OUTPUT_COLS)); } return null; } return RuleRaiseAccess.performRaise(rootNode, accessNode, parentNode); } static Expression op(String op, Expression expr1, Expression expr2, FunctionLibrary functionLibrary) { if (expr1 == null) { return expr2; } if (expr2 == null) { return expr1; } Function newExpr = new Function(op, new Expression[] {expr1, expr2}); newExpr.setFunctionDescriptor(functionLibrary.findFunction(op, new Class[] {DataTypeManager.DefaultDataClasses.INTEGER, DataTypeManager.DefaultDataClasses.INTEGER})); newExpr.setType(newExpr.getFunctionDescriptor().getReturnType()); return evaluateIfPossible(newExpr); } private static Expression evaluateIfPossible(Expression newExpr) { if (EvaluatableVisitor.isFullyEvaluatable(newExpr, true)) { try { return new Constant(Evaluator.evaluate(newExpr), newExpr.getType()); } catch (TeiidException e) { throw new TeiidRuntimeException(QueryPlugin.Event.TEIID30269, e, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30269)); } } return newExpr; } /** * @param limitNode * @param child * @throws TeiidComponentException * @throws BlockedException * @throws ExpressionEvaluationException */ static Expression getMinValue(Expression expr1, Expression expr2) { if (expr1 == null) { return expr2; } if (expr2 == null) { return expr1; } Criteria crit = new CompareCriteria(expr1, CompareCriteria.LT, expr2); SearchedCaseExpression sce = new SearchedCaseExpression(Arrays.asList(new Object[] {crit}), Arrays.asList(new Object[] {expr1})); sce.setElseExpression(expr2); sce.setType(expr1.getType()); return evaluateIfPossible(sce); } /** * @see java.lang.Object#toString() */ public String toString() { return "PushLimit"; //$NON-NLS-1$ } }