/*
* 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;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import org.teiid.adminapi.impl.ModelMetaData;
import org.teiid.adminapi.impl.VDBMetaData;
import org.teiid.api.exception.query.QueryMetadataException;
import org.teiid.api.exception.query.QueryPlannerException;
import org.teiid.core.CoreConstants;
import org.teiid.core.TeiidComponentException;
import org.teiid.core.TeiidProcessingException;
import org.teiid.core.TeiidRuntimeException;
import org.teiid.core.id.IDGenerator;
import org.teiid.core.util.Assertion;
import org.teiid.metadata.FunctionMethod.Determinism;
import org.teiid.metadata.FunctionMethod.PushDown;
import org.teiid.query.QueryPlugin;
import org.teiid.query.analysis.AnalysisRecord;
import org.teiid.query.metadata.QueryMetadataInterface;
import org.teiid.query.metadata.TempMetadataAdapter;
import org.teiid.query.metadata.TempMetadataID;
import org.teiid.query.optimizer.capabilities.CapabilitiesFinder;
import org.teiid.query.optimizer.capabilities.SourceCapabilities;
import org.teiid.query.optimizer.capabilities.SourceCapabilities.Capability;
import org.teiid.query.optimizer.relational.plantree.NodeConstants;
import org.teiid.query.optimizer.relational.plantree.NodeConstants.Info;
import org.teiid.query.optimizer.relational.plantree.PlanNode;
import org.teiid.query.optimizer.relational.rules.CapabilitiesUtil;
import org.teiid.query.optimizer.relational.rules.CriteriaCapabilityValidatorVisitor;
import org.teiid.query.optimizer.relational.rules.NewCalculateCostUtil;
import org.teiid.query.optimizer.relational.rules.RuleAssignOutputElements;
import org.teiid.query.optimizer.relational.rules.RuleChooseJoinStrategy;
import org.teiid.query.processor.ProcessorPlan;
import org.teiid.query.processor.RegisterRequestParameter;
import org.teiid.query.processor.relational.*;
import org.teiid.query.processor.relational.JoinNode.JoinStrategyType;
import org.teiid.query.processor.relational.MergeJoinStrategy.SortOption;
import org.teiid.query.processor.relational.SortUtility.Mode;
import org.teiid.query.resolver.util.ResolverUtil;
import org.teiid.query.sql.lang.*;
import org.teiid.query.sql.lang.ObjectTable.ObjectColumn;
import org.teiid.query.sql.lang.SetQuery.Operation;
import org.teiid.query.sql.lang.SourceHint.SpecificHint;
import org.teiid.query.sql.lang.XMLTable.XMLColumn;
import org.teiid.query.sql.navigator.DeepPreOrderNavigator;
import org.teiid.query.sql.symbol.AggregateSymbol;
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.ExpressionSymbol;
import org.teiid.query.sql.symbol.GroupSymbol;
import org.teiid.query.sql.symbol.Reference;
import org.teiid.query.sql.symbol.WindowFunction;
import org.teiid.query.sql.util.SymbolMap;
import org.teiid.query.sql.visitor.ElementCollectorVisitor;
import org.teiid.query.sql.visitor.EvaluatableVisitor;
import org.teiid.query.sql.visitor.EvaluatableVisitor.EvaluationLevel;
import org.teiid.query.sql.visitor.GroupCollectorVisitor;
import org.teiid.query.sql.visitor.ReferenceCollectorVisitor;
import org.teiid.query.util.CommandContext;
import org.teiid.translator.ExecutionFactory.TransactionSupport;
public class PlanToProcessConverter {
protected QueryMetadataInterface metadata;
private IDGenerator idGenerator;
private AnalysisRecord analysisRecord;
private CapabilitiesFinder capFinder;
//state for detecting and reusing source queries
private Map<Command, AccessNode> sharedCommands = new HashMap<Command, AccessNode>();
private CommandContext context;
private static AtomicInteger sharedId = new AtomicInteger();
public static class SharedStateKey {
int id;
int expectedReaders;
}
public PlanToProcessConverter(QueryMetadataInterface metadata, IDGenerator idGenerator, AnalysisRecord analysisRecord, CapabilitiesFinder capFinder, CommandContext context) {
this.metadata = metadata;
this.idGenerator = idGenerator;
this.analysisRecord = analysisRecord;
this.capFinder = capFinder;
this.context = context;
}
public RelationalPlan convert(PlanNode planNode)
throws QueryPlannerException, TeiidComponentException {
try {
boolean debug = analysisRecord.recordDebug();
if(debug) {
analysisRecord.println("\n============================================================================"); //$NON-NLS-1$
analysisRecord.println("CONVERTING PLAN TREE TO PROCESS TREE"); //$NON-NLS-1$
}
// Convert plan tree nodes into process tree nodes
RelationalNode processNode;
try {
processNode = convertPlan(planNode);
} catch (TeiidProcessingException e) {
if (e instanceof QueryPlannerException) {
throw (QueryPlannerException)e;
}
throw new QueryPlannerException(e);
}
if(debug) {
analysisRecord.println("\nPROCESS PLAN = \n" + processNode); //$NON-NLS-1$
analysisRecord.println("============================================================================"); //$NON-NLS-1$
}
RelationalPlan processPlan = new RelationalPlan(processNode);
return processPlan;
} finally {
sharedCommands.clear();
}
}
private RelationalNode convertPlan(PlanNode planNode)
throws TeiidComponentException, TeiidProcessingException {
// Convert current node in planTree
RelationalNode convertedNode = convertNode(planNode);
if(convertedNode == null) {
Assertion.assertTrue(planNode.getChildCount() == 1);
return convertPlan(planNode.getFirstChild());
}
RelationalNode nextParent = convertedNode;
// convertedNode may be the head of 1 or more nodes - go to end of chain
while(nextParent.getChildren()[0] != null) {
nextParent = nextParent.getChildren()[0];
}
// Call convertPlan recursively on children
for (PlanNode childNode : planNode.getChildren()) {
RelationalNode child = convertPlan(childNode);
if (planNode.getType() == NodeConstants.Types.SET_OP && nextParent instanceof UnionAllNode
&& childNode.getProperty(Info.SET_OPERATION) == planNode.getProperty(Info.SET_OPERATION)
&& childNode.getType() == NodeConstants.Types.SET_OP && childNode.hasBooleanProperty(Info.USE_ALL)) {
for (RelationalNode grandChild : child.getChildren()) {
if (grandChild != null) {
nextParent.addChild(grandChild);
}
}
} else {
nextParent.addChild(child);
}
}
// Return root of tree for top node
return convertedNode;
}
protected int getID() {
return idGenerator.nextInt();
}
protected RelationalNode convertNode(PlanNode node)
throws TeiidComponentException, TeiidProcessingException {
RelationalNode processNode = null;
switch(node.getType()) {
case NodeConstants.Types.PROJECT:
GroupSymbol intoGroup = (GroupSymbol) node.getProperty(NodeConstants.Info.INTO_GROUP);
if(intoGroup != null) {
try {
Insert insert = (Insert)node.getFirstChild().getProperty(Info.VIRTUAL_COMMAND);
List<ElementSymbol> allIntoElements = insert.getVariables();
Object groupID = intoGroup.getMetadataID();
Object modelID = metadata.getModelID(groupID);
String modelName = metadata.getFullName(modelID);
if (metadata.isVirtualGroup(groupID) && !metadata.isTemporaryTable(groupID)) {
InsertPlanExecutionNode ipen = new InsertPlanExecutionNode(getID(), metadata);
ProcessorPlan plan = (ProcessorPlan)node.getFirstChild().getProperty(Info.PROCESSOR_PLAN);
Assertion.isNotNull(plan);
ipen.setProcessorPlan(plan);
ipen.setReferences(insert.getValues());
processNode = ipen;
} else {
ProjectIntoNode pinode = new ProjectIntoNode(getID());
pinode.setIntoGroup(intoGroup);
pinode.setIntoElements(allIntoElements);
pinode.setModelName(modelName);
pinode.setConstraint((Criteria) node.getProperty(Info.CONSTRAINT));
pinode.setSourceHint((SourceHint) node.getProperty(Info.SOURCE_HINT));
if (node.hasBooleanProperty(Info.UPSERT)) {
pinode.setUpsert(true);
}
processNode = pinode;
SourceCapabilities caps = capFinder.findCapabilities(modelName);
if (caps.supportsCapability(Capability.INSERT_WITH_ITERATOR)) {
pinode.setMode(org.teiid.query.processor.relational.ProjectIntoNode.Mode.ITERATOR);
} else if (caps.supportsCapability(Capability.BATCHED_UPDATES)) {
pinode.setMode(org.teiid.query.processor.relational.ProjectIntoNode.Mode.BATCH);
} else {
pinode.setMode(org.teiid.query.processor.relational.ProjectIntoNode.Mode.SINGLE);
}
pinode.setTransactionSupport((TransactionSupport) caps.getSourceProperty(Capability.TRANSACTION_SUPPORT));
}
} catch(QueryMetadataException e) {
throw new TeiidComponentException(QueryPlugin.Event.TEIID30247, e);
}
} else {
List<Expression> symbols = (List) node.getProperty(NodeConstants.Info.PROJECT_COLS);
ProjectNode pnode = new ProjectNode(getID());
pnode.setSelectSymbols(symbols);
processNode = pnode;
if (node.hasBooleanProperty(Info.HAS_WINDOW_FUNCTIONS)) {
WindowFunctionProjectNode wfpn = new WindowFunctionProjectNode(getID());
//with partial projection the window function may already be pushed, we'll check for that here
ArrayList<Expression> filtered = new ArrayList<Expression>();
List<Expression> childSymbols = (List) node.getFirstChild().getProperty(NodeConstants.Info.OUTPUT_COLS);
for (Expression ex : symbols) {
ex = SymbolMap.getExpression(ex);
if (childSymbols.contains(ex)) {
continue;
}
filtered.add(ex);
}
Set<WindowFunction> windowFunctions = RuleAssignOutputElements.getWindowFunctions(filtered);
if (!windowFunctions.isEmpty()) {
//TODO: check for selecting all window functions
List<Expression> outputElements = new ArrayList<Expression>(windowFunctions);
//collect the other projected expressions
for (Expression singleElementSymbol : (List<Expression>)node.getFirstChild().getProperty(Info.OUTPUT_COLS)) {
outputElements.add(singleElementSymbol);
}
wfpn.setElements(outputElements);
wfpn.init();
pnode.addChild(wfpn);
for (WindowFunction wf : windowFunctions) {
validateAggregateFunctionEvaluation(wf.getFunction());
}
}
}
}
break;
case NodeConstants.Types.JOIN:
JoinType jtype = (JoinType) node.getProperty(NodeConstants.Info.JOIN_TYPE);
JoinStrategyType stype = (JoinStrategyType) node.getProperty(NodeConstants.Info.JOIN_STRATEGY);
JoinNode jnode = new JoinNode(getID());
jnode.setJoinType(jtype);
jnode.setLeftDistinct(node.hasBooleanProperty(NodeConstants.Info.IS_LEFT_DISTINCT));
jnode.setRightDistinct(node.hasBooleanProperty(NodeConstants.Info.IS_RIGHT_DISTINCT));
List joinCrits = (List) node.getProperty(NodeConstants.Info.JOIN_CRITERIA);
String depValueSource = (String) node.getProperty(NodeConstants.Info.DEPENDENT_VALUE_SOURCE);
SortOption leftSort = (SortOption)node.getProperty(NodeConstants.Info.SORT_LEFT);
if(stype == JoinStrategyType.MERGE || stype == JoinStrategyType.ENHANCED_SORT) {
MergeJoinStrategy mjStrategy = null;
if (stype.equals(JoinStrategyType.ENHANCED_SORT)) {
EnhancedSortMergeJoinStrategy esmjStrategy = new EnhancedSortMergeJoinStrategy(leftSort, (SortOption)node.getProperty(NodeConstants.Info.SORT_RIGHT));
esmjStrategy.setSemiDep(node.hasBooleanProperty(Info.IS_SEMI_DEP));
mjStrategy = esmjStrategy;
} else {
mjStrategy = new MergeJoinStrategy(leftSort, (SortOption)node.getProperty(NodeConstants.Info.SORT_RIGHT), false);
}
jnode.setJoinStrategy(mjStrategy);
List leftExpressions = (List) node.getProperty(NodeConstants.Info.LEFT_EXPRESSIONS);
List rightExpressions = (List) node.getProperty(NodeConstants.Info.RIGHT_EXPRESSIONS);
jnode.setJoinExpressions(leftExpressions, rightExpressions);
joinCrits = (List) node.getProperty(NodeConstants.Info.NON_EQUI_JOIN_CRITERIA);
} else if (stype == JoinStrategyType.NESTED_TABLE) {
NestedTableJoinStrategy ntjStrategy = new NestedTableJoinStrategy();
jnode.setJoinStrategy(ntjStrategy);
SymbolMap references = (SymbolMap)node.getProperty(Info.LEFT_NESTED_REFERENCES);
ntjStrategy.setLeftMap(references);
references = (SymbolMap)node.getProperty(Info.RIGHT_NESTED_REFERENCES);
ntjStrategy.setRightMap(references);
} else {
NestedLoopJoinStrategy nljStrategy = new NestedLoopJoinStrategy();
jnode.setJoinStrategy(nljStrategy);
}
Criteria joinCrit = Criteria.combineCriteria(joinCrits);
jnode.setJoinCriteria(joinCrit);
processNode = jnode;
jnode.setDependentValueSource(depValueSource);
break;
case NodeConstants.Types.ACCESS:
ProcessorPlan plan = (ProcessorPlan) node.getProperty(NodeConstants.Info.PROCESSOR_PLAN);
if(plan != null) {
PlanExecutionNode peNode = null;
Criteria crit = (Criteria)node.getProperty(NodeConstants.Info.PROCEDURE_CRITERIA);
if (crit != null) {
List references = (List)node.getProperty(NodeConstants.Info.PROCEDURE_INPUTS);
List defaults = (List)node.getProperty(NodeConstants.Info.PROCEDURE_DEFAULTS);
peNode = new DependentProcedureExecutionNode(getID(), crit, references, defaults);
} else {
peNode = new PlanExecutionNode(getID());
}
peNode.setProcessorPlan(plan);
processNode = peNode;
} else {
AccessNode aNode = null;
Command command = (Command) node.getProperty(NodeConstants.Info.ATOMIC_REQUEST);
Object modelID = node.getProperty(NodeConstants.Info.MODEL_ID);
if (modelID != null) {
String fullName = metadata.getFullName(modelID);
if (!capFinder.isValid(fullName)) {
//TODO: we ideally want to handle the partial resutls case here differently
// by adding a null node / and a source warning
// for now it's just as easy to say that the user needs to take steps to
// return static capabilities
SourceCapabilities caps = capFinder.findCapabilities(fullName);
Exception cause = null;
if (caps != null) {
cause = (Exception) caps.getSourceProperty(Capability.INVALID_EXCEPTION);
}
throw new QueryPlannerException(QueryPlugin.Event.TEIID30498, cause, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30498, fullName));
}
}
EvaluatableVisitor ev = null;
if(node.hasBooleanProperty(NodeConstants.Info.IS_DEPENDENT_SET)) {
if (command instanceof StoredProcedure) {
List references = (List)node.getProperty(NodeConstants.Info.PROCEDURE_INPUTS);
List defaults = (List)node.getProperty(NodeConstants.Info.PROCEDURE_DEFAULTS);
Criteria crit = (Criteria)node.getProperty(NodeConstants.Info.PROCEDURE_CRITERIA);
DependentProcedureAccessNode depAccessNode = new DependentProcedureAccessNode(getID(), crit, references, defaults);
processNode = depAccessNode;
aNode = depAccessNode;
} else {
//create dependent access node
DependentAccessNode depAccessNode = new DependentAccessNode(getID());
if(modelID != null){
depAccessNode.setPushdown(CapabilitiesUtil.supports(Capability.DEPENDENT_JOIN, modelID, metadata, capFinder));
depAccessNode.setMaxSetSize(CapabilitiesUtil.getMaxInCriteriaSize(modelID, metadata, capFinder));
depAccessNode.setMaxPredicates(CapabilitiesUtil.getMaxDependentPredicates(modelID, metadata, capFinder));
depAccessNode.setUseBindings(CapabilitiesUtil.supports(Capability.DEPENDENT_JOIN_BINDINGS, modelID, metadata, capFinder));
//TODO: allow the translator to drive this property
//simplistic check of whether this query is complex to re-execute
Query query = (Query)command;
if (query.getGroupBy() != null
|| query.getFrom().getClauses().size() > 1
|| !(query.getFrom().getClauses().get(0) instanceof UnaryFromClause)
|| query.getWith() != null) {
depAccessNode.setComplexQuery(true);
} else {
//check to see if there in an index on at least one of the dependent sets
Set<GroupSymbol> groups = new HashSet<GroupSymbol>(query.getFrom().getGroups());
boolean found = false;
for (Criteria crit : Criteria.separateCriteriaByAnd(query.getCriteria())) {
if (crit instanceof DependentSetCriteria) {
DependentSetCriteria dsc = (DependentSetCriteria)crit;
if (NewCalculateCostUtil.getKeyUsed(ElementCollectorVisitor.getElements(dsc.getExpression(), true), groups, metadata, null) != null) {
found = true;
break;
}
}
}
if (!found) {
depAccessNode.setComplexQuery(true);
}
}
}
processNode = depAccessNode;
aNode = depAccessNode;
}
aNode.setShouldEvaluateExpressions(true);
} else {
// create access node
aNode = new AccessNode(getID());
processNode = aNode;
}
//-- special handling for system tables. currently they cannot perform projection
try {
if (command instanceof Query) {
processNode = correctProjectionInternalTables(node, aNode);
}
} catch (QueryMetadataException err) {
throw new TeiidComponentException(QueryPlugin.Event.TEIID30248, err);
}
setRoutingName(aNode, node, command);
boolean shouldEval = false;
if (command instanceof Insert) {
Insert insert = (Insert)command;
if (insert.getQueryExpression() != null) {
insert.setQueryExpression((QueryCommand)aliasCommand(aNode, insert.getQueryExpression(), modelID));
} else {
for (int i = 0; i < insert.getValues().size(); i++) {
Expression ex = (Expression)insert.getValues().get(i);
if (!CriteriaCapabilityValidatorVisitor.canPushLanguageObject(ex, modelID, metadata, capFinder, analysisRecord)) {
//replace with an expression symbol to let the rewriter know that it should be replaced
insert.getValues().set(i, new ExpressionSymbol("x", ex));
shouldEval = true;
}
}
}
} else if (command instanceof QueryCommand) {
command = aliasCommand(aNode, command, modelID);
}
ev = EvaluatableVisitor.needsEvaluationVisitor(modelID, metadata, capFinder);
if (!shouldEval && modelID != null) {
//do a capabilities sensitive check for needs eval
String modelName = metadata.getFullName(modelID);
SourceCapabilities caps = capFinder.findCapabilities(modelName);
final CriteriaCapabilityValidatorVisitor capVisitor = new CriteriaCapabilityValidatorVisitor(modelID, metadata, capFinder, caps);
capVisitor.setCheckEvaluation(false);
DeepPreOrderNavigator nav = new DeepPreOrderNavigator(ev) {
protected void visitNode(org.teiid.query.sql.LanguageObject obj) {
if (capVisitor.isValid() && obj instanceof Expression) {
obj.acceptVisitor(capVisitor);
}
super.visitNode(obj);
}
};
command.acceptVisitor(nav);
if (!capVisitor.isValid()) {
//there's a non-supported construct pushed, we should eval
ev.evaluationNotPossible(EvaluationLevel.PROCESSING);
}
} else {
DeepPreOrderNavigator.doVisit(command, ev);
}
aNode.setShouldEvaluateExpressions(ev.requiresEvaluation(EvaluationLevel.PROCESSING) || shouldEval);
aNode.setCommand(command);
if (modelID != null) {
String fullName = metadata.getFullName(modelID);
SourceCapabilities caps = capFinder.findCapabilities(fullName);
aNode.setTransactionSupport((TransactionSupport) caps.getSourceProperty(Capability.TRANSACTION_SUPPORT));
}
Map<GroupSymbol, PlanNode> subPlans = (Map<GroupSymbol, PlanNode>) node.getProperty(Info.SUB_PLANS);
//it makes more sense to allow the multisource affect to be elevated above just access nodes
if (aNode.getModelId() != null && metadata.isMultiSource(aNode.getModelId())) {
VDBMetaData vdb = context.getVdb();
aNode.setShouldEvaluateExpressions(true); //forces a rewrite
aNode.setElements( (List) node.getProperty(NodeConstants.Info.OUTPUT_COLS) );
if (node.hasBooleanProperty(Info.IS_MULTI_SOURCE)) {
Expression ex = rewriteMultiSourceCommand(aNode.getCommand());
aNode.setConnectorBindingExpression(ex);
aNode.setMultiSource(true);
} else {
String sourceName = (String)node.getProperty(Info.SOURCE_NAME);
aNode.setConnectorBindingExpression(new Constant(sourceName));
}
} else if (subPlans == null){
if (!aNode.isShouldEvaluate()) {
aNode.minimizeProject(command);
}
//check if valid to share this with other nodes
if (ev != null && ev.getDeterminismLevel().compareTo(Determinism.COMMAND_DETERMINISTIC) >= 0 && command.areResultsCachable()) {
checkForSharedSourceCommand(aNode);
}
}
if (subPlans != null) {
QueryCommand qc = (QueryCommand)command;
if (qc.getWith() == null) {
qc.setWith(new ArrayList<WithQueryCommand>(subPlans.size()));
}
Map<GroupSymbol, RelationalPlan> plans = new LinkedHashMap<GroupSymbol, RelationalPlan>();
for (Map.Entry<GroupSymbol, PlanNode> entry : subPlans.entrySet()) {
RelationalPlan subPlan = convert(entry.getValue());
List<ElementSymbol> elems = ResolverUtil.resolveElementsInGroup(entry.getKey(), metadata);
subPlan.setOutputElements(elems);
plans.put(entry.getKey(), subPlan);
WithQueryCommand withQueryCommand = new WithQueryCommand(entry.getKey(), elems, null);
qc.getWith().add(withQueryCommand);
}
aNode.setSubPlans(plans);
}
}
break;
case NodeConstants.Types.SELECT:
Criteria crit = (Criteria) node.getProperty(NodeConstants.Info.SELECT_CRITERIA);
if (!node.hasCollectionProperty(Info.OUTPUT_COLS)) {
//the late optimization to create a dependent join from a subquery introduces
//criteria that have no output elements set
//TODO that should be cleaner, but the logic currently expects to be run after join implementation
//and rerunning assign output elements seems excessive
node.setProperty(Info.OUTPUT_COLS, node.getFirstChild().getProperty(Info.OUTPUT_COLS));
}
SelectNode selnode = new SelectNode(getID());
selnode.setCriteria(crit);
//in case the parent was a source
selnode.setProjectedExpressions((List<Expression>) node.getProperty(NodeConstants.Info.PROJECT_COLS));
processNode = selnode;
break;
case NodeConstants.Types.SORT:
case NodeConstants.Types.DUP_REMOVE:
if (node.getType() == NodeConstants.Types.DUP_REMOVE) {
processNode = new DupRemoveNode(getID());
} else {
SortNode sortNode = new SortNode(getID());
OrderBy orderBy = (OrderBy) node.getProperty(NodeConstants.Info.SORT_ORDER);
if (orderBy != null) {
sortNode.setSortElements(orderBy.getOrderByItems());
}
if (node.hasBooleanProperty(NodeConstants.Info.IS_DUP_REMOVAL)) {
sortNode.setMode(Mode.DUP_REMOVE_SORT);
}
processNode = sortNode;
}
break;
case NodeConstants.Types.GROUP:
GroupingNode gnode = new GroupingNode(getID());
gnode.setRollup(node.hasBooleanProperty(Info.ROLLUP));
SymbolMap groupingMap = (SymbolMap)node.getProperty(NodeConstants.Info.SYMBOL_MAP);
gnode.setOutputMapping(groupingMap);
gnode.setRemoveDuplicates(node.hasBooleanProperty(NodeConstants.Info.IS_DUP_REMOVAL));
List<Expression> gCols = (List) node.getProperty(NodeConstants.Info.GROUP_COLS);
OrderBy orderBy = (OrderBy) node.getProperty(Info.SORT_ORDER);
if (orderBy == null) {
if (gCols != null) {
LinkedHashSet<Expression> exprs = new LinkedHashSet<Expression>();
for (Expression ex : gCols) {
exprs.add(SymbolMap.getExpression(ex));
}
orderBy = new OrderBy(RuleChooseJoinStrategy.createExpressionSymbols(new ArrayList<Expression>(exprs)));
}
} else {
HashSet<Expression> seen = new HashSet<Expression>();
for (int i = 0; i < gCols.size(); i++) {
if (i < orderBy.getOrderByItems().size()) {
OrderByItem orderByItem = orderBy.getOrderByItems().get(i);
Expression ex = SymbolMap.getExpression(orderByItem.getSymbol());
if (!seen.add(ex)) {
continue;
}
if (ex instanceof ElementSymbol) {
ex = groupingMap.getMappedExpression((ElementSymbol) ex);
orderByItem.setSymbol(new ExpressionSymbol("expr", ex)); //$NON-NLS-1$
}
} else {
orderBy.addVariable(new ExpressionSymbol("expr", gCols.get(i)), OrderBy.ASC); //$NON-NLS-1$
}
}
}
if (orderBy != null) {
gnode.setOrderBy(orderBy.getOrderByItems());
}
for (Expression ex : groupingMap!=null?groupingMap.getValues():(List<Expression>)node.getFirstChild().getProperty(NodeConstants.Info.PROJECT_COLS)) {
if (ex instanceof AggregateSymbol) {
validateAggregateFunctionEvaluation((AggregateSymbol)ex);
}
}
processNode = gnode;
break;
case NodeConstants.Types.SOURCE:
Object source = node.getProperty(NodeConstants.Info.TABLE_FUNCTION);
if (source instanceof XMLTable) {
XMLTable xt = (XMLTable)source;
XMLTableNode xtn = new XMLTableNode(getID());
//we handle the projection filtering once here rather than repeating the
//path analysis on a per plan basis
updateGroupName(node, xt);
Map<Expression, Integer> elementMap = RelationalNode.createLookupMap(xt.getProjectedSymbols());
List cols = (List) node.getProperty(NodeConstants.Info.OUTPUT_COLS);
int[] projectionIndexes = RelationalNode.getProjectionIndexes(elementMap, cols);
ArrayList<XMLColumn> filteredColumns = new ArrayList<XMLColumn>(projectionIndexes.length);
for (int col : projectionIndexes) {
filteredColumns.add(xt.getColumns().get(col));
}
xt.getXQueryExpression().useDocumentProjection(filteredColumns, analysisRecord);
xtn.setProjectedColumns(filteredColumns);
xtn.setTable(xt);
processNode = xtn;
break;
}
if (source instanceof ObjectTable) {
ObjectTable ot = (ObjectTable)source;
ObjectTableNode otn = new ObjectTableNode(getID());
//we handle the projection filtering once here rather than repeating the
//path analysis on a per plan basis
updateGroupName(node, ot);
Map<Expression, Integer> elementMap = RelationalNode.createLookupMap(ot.getProjectedSymbols());
List<Expression> cols = (List<Expression>) node.getProperty(NodeConstants.Info.OUTPUT_COLS);
int[] projectionIndexes = RelationalNode.getProjectionIndexes(elementMap, cols);
ArrayList<ObjectColumn> filteredColumns = new ArrayList<ObjectColumn>(projectionIndexes.length);
for (int col : projectionIndexes) {
filteredColumns.add(ot.getColumns().get(col));
}
otn.setProjectedColumns(filteredColumns);
otn.setTable(ot);
processNode = otn;
break;
}
if (source instanceof TextTable) {
TextTableNode ttn = new TextTableNode(getID());
TextTable tt = (TextTable)source;
updateGroupName(node, tt);
ttn.setTable(tt);
processNode = ttn;
break;
}
if (source instanceof ArrayTable) {
ArrayTableNode atn = new ArrayTableNode(getID());
ArrayTable at = (ArrayTable)source;
updateGroupName(node, at);
atn.setTable(at);
processNode = atn;
break;
}
SymbolMap symbolMap = (SymbolMap) node.getProperty(NodeConstants.Info.SYMBOL_MAP);
if(symbolMap != null) {
PlanNode child = node.getLastChild();
if (child.getType() == NodeConstants.Types.PROJECT
|| child.getType() == NodeConstants.Types.SELECT) {
//update the project cols based upon the original output
child.setProperty(NodeConstants.Info.PROJECT_COLS, child.getProperty(NodeConstants.Info.OUTPUT_COLS));
}
child.setProperty(NodeConstants.Info.OUTPUT_COLS, node.getProperty(NodeConstants.Info.OUTPUT_COLS));
}
return null;
case NodeConstants.Types.SET_OP:
Operation setOp = (Operation) node.getProperty(NodeConstants.Info.SET_OPERATION);
boolean useAll = ((Boolean) node.getProperty(NodeConstants.Info.USE_ALL)).booleanValue();
if(setOp == Operation.UNION) {
RelationalNode unionAllNode = new UnionAllNode(getID());
if(useAll) {
processNode = unionAllNode;
} else {
boolean onlyDupRemoval = node.hasBooleanProperty(NodeConstants.Info.IS_DUP_REMOVAL);
if (onlyDupRemoval) {
processNode = new DupRemoveNode(getID());
} else {
SortNode sNode = new SortNode(getID());
sNode.setMode(Mode.DUP_REMOVE_SORT);
processNode = sNode;
}
unionAllNode.setElements( (List) node.getProperty(NodeConstants.Info.OUTPUT_COLS) );
processNode.addChild(unionAllNode);
}
} else {
JoinNode joinAsSet = new JoinNode(getID());
joinAsSet.setJoinStrategy(new MergeJoinStrategy(SortOption.SORT_DISTINCT, SortOption.SORT_DISTINCT, true));
//If we push these sorts, we will have to enforce null order, since nulls are equal here
List leftExpressions = (List) node.getFirstChild().getProperty(NodeConstants.Info.OUTPUT_COLS);
List rightExpressions = (List) node.getLastChild().getProperty(NodeConstants.Info.OUTPUT_COLS);
joinAsSet.setJoinType(setOp == Operation.EXCEPT ? JoinType.JOIN_ANTI_SEMI : JoinType.JOIN_SEMI);
joinAsSet.setJoinExpressions(leftExpressions, rightExpressions);
processNode = joinAsSet;
}
break;
case NodeConstants.Types.TUPLE_LIMIT:
Expression rowLimit = (Expression)node.getProperty(NodeConstants.Info.MAX_TUPLE_LIMIT);
Expression offset = (Expression)node.getProperty(NodeConstants.Info.OFFSET_TUPLE_COUNT);
LimitNode ln = new LimitNode(getID(), rowLimit, offset);
ln.setImplicit(node.hasBooleanProperty(Info.IS_IMPLICIT_LIMIT));
processNode = ln;
break;
case NodeConstants.Types.NULL:
processNode = new NullNode(getID());
break;
default:
throw new QueryPlannerException(QueryPlugin.Event.TEIID30250, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30250, NodeConstants.getNodeTypeString(node.getType())));
}
if(processNode != null) {
processNode = prepareToAdd(node, processNode);
}
return processNode;
}
private void validateAggregateFunctionEvaluation(AggregateSymbol as) throws QueryPlannerException {
if (as.getFunctionDescriptor() != null && as.getFunctionDescriptor().getPushdown() == PushDown.MUST_PUSHDOWN) {
throw new QueryPlannerException(QueryPlugin.Event.TEIID31211, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31211, as.getFunctionDescriptor().getFullName()));
}
}
private Command aliasCommand(AccessNode aNode, Command command,
Object modelID) throws TeiidComponentException,
QueryPlannerException {
try {
command = (Command)command.clone();
boolean aliasGroups = modelID != null && (CapabilitiesUtil.supportsGroupAliases(modelID, metadata, capFinder)
|| CapabilitiesUtil.supports(Capability.QUERY_FROM_INLINE_VIEWS, modelID, metadata, capFinder));
boolean aliasColumns = modelID != null && (CapabilitiesUtil.supports(Capability.QUERY_SELECT_EXPRESSION, modelID, metadata, capFinder)
|| CapabilitiesUtil.supports(Capability.QUERY_FROM_INLINE_VIEWS, modelID, metadata, capFinder));
AliasGenerator visitor = new AliasGenerator(aliasGroups, !aliasColumns);
SourceHint sh = command.getSourceHint();
if (sh != null && aliasGroups) {
VDBMetaData vdb = context.getDQPWorkContext().getVDB();
ModelMetaData model = vdb.getModel(aNode.getModelName());
List<String> sourceNames = model.getSourceNames();
SpecificHint sp = null;
if (sourceNames.size() == 1) {
sp = sh.getSpecificHint(sourceNames.get(0));
}
if (sh.isUseAliases() || (sp != null && sp.isUseAliases())) {
visitor.setAliasMapping(context.getAliasMapping());
}
}
List<Reference> references = ReferenceCollectorVisitor.getReferences(command);
if (!references.isEmpty()) {
Set<String> correleatedGroups = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
for (Reference ref : references) {
if (ref.isCorrelated() && ref.getExpression().getGroupSymbol() != null) {
correleatedGroups.add(ref.getExpression().getGroupSymbol().getName());
}
}
visitor.setCorrelationGroups(correleatedGroups);
}
command.acceptVisitor(visitor);
} catch (QueryMetadataException err) {
throw new TeiidComponentException(QueryPlugin.Event.TEIID30249, err);
} catch (TeiidRuntimeException e) {
if (e.getCause() instanceof QueryPlannerException) {
throw (QueryPlannerException)e.getCause();
}
throw e;
}
return command;
}
private void checkForSharedSourceCommand(AccessNode aNode) {
//create a top level key to avoid the full command toString
String modelName = aNode.getModelName();
Command cmd = aNode.getCommand();
//don't share full scans against internal sources, it's a waste of buffering
if (CoreConstants.SYSTEM_MODEL.equals(modelName)
|| CoreConstants.SYSTEM_ADMIN_MODEL.equals(modelName)
|| TempMetadataAdapter.TEMP_MODEL.getName().equals(modelName)) {
if (!(cmd instanceof Query)) {
return;
}
Query query = (Query)cmd;
if (query.getOrderBy() == null && query.getCriteria() == null) {
return;
}
}
AccessNode other = sharedCommands.get(cmd);
if (other == null) {
sharedCommands.put(cmd, aNode);
} else {
if (other.info == null) {
other.info = new RegisterRequestParameter.SharedAccessInfo();
other.info.id = sharedId.getAndIncrement();
}
other.info.sharingCount++;
aNode.info = other.info;
}
}
private void updateGroupName(PlanNode node, TableFunctionReference tt) {
String groupName = node.getGroups().iterator().next().getName();
tt.getGroupSymbol().setName(groupName);
for (ElementSymbol symbol : tt.getProjectedSymbols()) {
symbol.setGroupSymbol(new GroupSymbol(groupName));
}
}
private RelationalNode correctProjectionInternalTables(PlanNode node,
AccessNode aNode) throws QueryMetadataException,
TeiidComponentException {
if (node.getGroups().size() != 1) {
return aNode;
}
GroupSymbol group = node.getGroups().iterator().next();
if (!CoreConstants.SYSTEM_MODEL.equals(metadata.getFullName(metadata.getModelID(group.getMetadataID())))
&& !CoreConstants.SYSTEM_ADMIN_MODEL.equals(metadata.getFullName(metadata.getModelID(group.getMetadataID())))) {
return aNode;
}
List projectSymbols = (List) node.getProperty(NodeConstants.Info.OUTPUT_COLS);
List<ElementSymbol> acutalColumns = ResolverUtil.resolveElementsInGroup(group, metadata);
if (projectSymbols.equals(acutalColumns)) {
return aNode;
}
node.setProperty(NodeConstants.Info.OUTPUT_COLS, acutalColumns);
if (node.getParent() != null && node.getParent().getType() == NodeConstants.Types.PROJECT) {
//if the parent is already a project, just correcting the output cols is enough
return aNode;
}
ProjectNode pnode = new ProjectNode(getID());
pnode.setSelectSymbols(projectSymbols);
aNode = (AccessNode)prepareToAdd(node, aNode);
node.setProperty(NodeConstants.Info.OUTPUT_COLS, projectSymbols);
pnode.addChild(aNode);
return pnode;
}
private RelationalNode prepareToAdd(PlanNode node,
RelationalNode processNode) {
// Set the output elements from the plan node
List cols = (List) node.getProperty(NodeConstants.Info.OUTPUT_COLS);
processNode.setElements(cols);
// Set the Cost Estimates
Number estimateNodeCardinality = (Number) node.getProperty(NodeConstants.Info.EST_CARDINALITY);
processNode.setEstimateNodeCardinality(estimateNodeCardinality);
Number estimateNodeSetSize = (Number) node.getProperty(NodeConstants.Info.EST_SET_SIZE);
processNode.setEstimateNodeSetSize(estimateNodeSetSize);
Number estimateDepAccessCardinality = (Number) node.getProperty(NodeConstants.Info.EST_DEP_CARDINALITY);
processNode.setEstimateDepAccessCardinality(estimateDepAccessCardinality);
Number estimateDepJoinCost = (Number) node.getProperty(NodeConstants.Info.EST_DEP_JOIN_COST);
processNode.setEstimateDepJoinCost(estimateDepJoinCost);
Number estimateJoinCost = (Number) node.getProperty(NodeConstants.Info.EST_JOIN_COST);
processNode.setEstimateJoinCost(estimateJoinCost);
return processNode;
}
private void setRoutingName(AccessNode accessNode, PlanNode node, Command command)
throws QueryPlannerException, TeiidComponentException {
// Look up connector binding name
try {
Object modelID = node.getProperty(NodeConstants.Info.MODEL_ID);
if(modelID == null || modelID instanceof TempMetadataID) {
if(command instanceof StoredProcedure){
modelID = ((StoredProcedure)command).getModelID();
}else if (!(command instanceof Create || command instanceof Drop)){
Collection<GroupSymbol> groups = GroupCollectorVisitor.getGroups(command, true);
GroupSymbol group = groups.iterator().next();
modelID = metadata.getModelID(group.getMetadataID());
}
}
String cbName = metadata.getFullName(modelID);
accessNode.setModelName(cbName);
accessNode.setModelId(modelID);
accessNode.setConformedTo((Set<Object>) node.getProperty(Info.CONFORMED_SOURCES));
} catch(QueryMetadataException e) {
throw new QueryPlannerException(QueryPlugin.Event.TEIID30251, e, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30251));
}
}
private Expression rewriteMultiSourceCommand(Command command) throws TeiidComponentException {
Expression result = null;
if (command instanceof StoredProcedure) {
StoredProcedure obj = (StoredProcedure)command;
for (Iterator<SPParameter> params = obj.getMapOfParameters().values().iterator(); params.hasNext();) {
SPParameter param = params.next();
if (param.getParameterType() != SPParameter.IN) {
continue;
}
if(metadata.isMultiSourceElement(param.getMetadataID())) {
Expression source = param.getExpression();
params.remove();
if (param.isUsingDefault() && source instanceof Constant && ((Constant)source).isNull()) {
continue;
}
result = source;
break;
}
}
} if (command instanceof Insert) {
Insert obj = (Insert)command;
for (int i = 0; i < obj.getVariables().size(); i++) {
ElementSymbol elem = obj.getVariables().get(i);
Object metadataID = elem.getMetadataID();
if(metadata.isMultiSourceElement(metadataID)) {
Expression source = (Expression)obj.getValues().get(i);
obj.getVariables().remove(i);
obj.getValues().remove(i);
result = source;
break;
}
}
} else if (command instanceof FilteredCommand) {
for (Criteria c : Criteria.separateCriteriaByAnd(((FilteredCommand) command).getCriteria())) {
if (!(c instanceof CompareCriteria)) {
continue;
}
CompareCriteria cc = (CompareCriteria)c;
if (cc.getLeftExpression() instanceof ElementSymbol) {
ElementSymbol es = (ElementSymbol)cc.getLeftExpression();
if (metadata.isMultiSourceElement(es.getMetadataID()) && EvaluatableVisitor.willBecomeConstant(cc.getRightExpression())) {
if (result != null && !result.equals(cc.getRightExpression())) {
return Constant.NULL_CONSTANT;
}
result = cc.getRightExpression();
}
}
}
}
return result;
}
}