/* * 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.Collection; import java.util.TreeSet; import org.teiid.api.exception.query.QueryMetadataException; import org.teiid.client.plan.Annotation; import org.teiid.client.plan.Annotation.Priority; import org.teiid.core.TeiidComponentException; import org.teiid.core.types.DataTypeManager; 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.SupportConstants; 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.processor.ProcessorPlan; import org.teiid.query.processor.relational.AccessNode; import org.teiid.query.processor.relational.LimitNode; import org.teiid.query.processor.relational.RelationalNode; import org.teiid.query.processor.relational.RelationalPlan; import org.teiid.query.sql.LanguageObject; import org.teiid.query.sql.LanguageVisitor; import org.teiid.query.sql.lang.*; import org.teiid.query.sql.navigator.PreOrPostOrderNavigator; import org.teiid.query.sql.symbol.*; import org.teiid.query.sql.util.SymbolMap; 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.util.CommandContext; import org.teiid.translator.ExecutionFactory.Format; import org.teiid.translator.SourceSystemFunctions; /** */ public class CriteriaCapabilityValidatorVisitor extends LanguageVisitor { // Initialization state private Object modelID; private QueryMetadataInterface metadata; private CapabilitiesFinder capFinder; private AnalysisRecord analysisRecord; // Retrieved during initialization and cached private SourceCapabilities caps; // Output state private TeiidComponentException exception; private boolean valid = true; private boolean isJoin; private boolean isSelectClause; private boolean checkEvaluation = true; /** * @param iterator * @throws TeiidComponentException * @throws QueryMetadataException */ public CriteriaCapabilityValidatorVisitor(Object modelID, QueryMetadataInterface metadata, CapabilitiesFinder capFinder, SourceCapabilities caps) throws QueryMetadataException, TeiidComponentException { this.modelID = modelID; this.metadata = metadata; this.capFinder = capFinder; this.caps = caps; } @Override public void visit(XMLAttributes obj) { if (willBecomeConstant(obj)) { return; } markInvalid(obj, "Pushdown of XMLAttributes not allowed"); //$NON-NLS-1$ } private boolean willBecomeConstant(LanguageObject obj) { return checkEvaluation && EvaluatableVisitor.willBecomeConstant(obj, true); } @Override public void visit(XMLNamespaces obj) { if (willBecomeConstant(obj)) { return; } markInvalid(obj, "Pushdown of XMLNamespaces not allowed"); //$NON-NLS-1$ } @Override public void visit(TextLine obj) { markInvalid(obj, "Pushdown of TextLine not allowed"); //$NON-NLS-1$ } @Override public void visit(XMLForest obj) { if (willBecomeConstant(obj)) { return; } markInvalid(obj, "Pushdown of XMLForest not allowed"); //$NON-NLS-1$ } @Override public void visit(JSONObject obj) { if (willBecomeConstant(obj)) { return; } markInvalid(obj, "Pushdown of JSONObject not allowed"); //$NON-NLS-1$ } @Override public void visit(XMLElement obj) { if (willBecomeConstant(obj)) { return; } markInvalid(obj, "Pushdown of XMLElement not allowed"); //$NON-NLS-1$ } @Override public void visit(XMLSerialize obj) { if (willBecomeConstant(obj)) { return; } markInvalid(obj, "Pushdown of XMLSerialize not allowed"); //$NON-NLS-1$ } @Override public void visit(XMLParse obj) { if (willBecomeConstant(obj)) { return; } markInvalid(obj, "Pushdown of XMLParse not allowed"); //$NON-NLS-1$ } @Override public void visit(XMLQuery obj) { if (willBecomeConstant(obj)) { return; } markInvalid(obj, "Pushdown of XMLQuery not allowed"); //$NON-NLS-1$ } @Override public void visit(XMLExists obj) { if (willBecomeConstant(obj)) { return; } markInvalid(obj, "Pushdown of XMLExists not allowed"); //$NON-NLS-1$ } public void visit(XMLCast xmlCast) { if (willBecomeConstant(xmlCast)) { return; } markInvalid(xmlCast, "Pushdown of XMLCast not allowed"); //$NON-NLS-1$ } @Override public void visit(QueryString obj) { if (willBecomeConstant(obj)) { return; } markInvalid(obj, "Pushdown of QueryString not allowed"); //$NON-NLS-1$ } @Override public void visit(Array array) { try { if (!CapabilitiesUtil.supports(Capability.ARRAY_TYPE, modelID, metadata, capFinder)) { markInvalid(array, "Array type not supported by source"); //$NON-NLS-1$ } else if (isSelectClause && !CapabilitiesUtil.supports(Capability.QUERY_SELECT_EXPRESSION_ARRAY_TYPE, modelID, metadata, capFinder)) { //TODO: this is just a workaround of sorts - as a comparison could be nested where this is allowed markInvalid(array, "Array type expression projection not supported by source"); //$NON-NLS-1$ } } catch (QueryMetadataException e) { handleException(new TeiidComponentException(e)); } catch(TeiidComponentException e) { handleException(e); } } public void visit(AggregateSymbol obj) { try { if(! CapabilitiesUtil.supportsAggregateFunction(modelID, obj, metadata, capFinder)) { markInvalid(obj, "Aggregate function pushdown not supported by source"); //$NON-NLS-1$ } } catch(QueryMetadataException e) { handleException(new TeiidComponentException(e)); } catch(TeiidComponentException e) { handleException(e); } } @Override public void visit(WindowFunction windowFunction) { if(! this.caps.supportsCapability(Capability.ELEMENTARY_OLAP)) { markInvalid(windowFunction, "Window function not supported by source"); //$NON-NLS-1$ return; } if (!this.caps.supportsCapability(Capability.WINDOW_FUNCTION_ORDER_BY_AGGREGATES) && windowFunction.getWindowSpecification().getOrderBy() != null && !windowFunction.getFunction().isAnalytical()) { markInvalid(windowFunction, "Window function order by with aggregate not supported by source"); //$NON-NLS-1$ return; } if (!this.caps.supportsCapability(Capability.WINDOW_FUNCTION_DISTINCT_AGGREGATES) && windowFunction.getFunction().isDistinct()) { markInvalid(windowFunction, "Window function distinct aggregate not supported by source"); //$NON-NLS-1$ return; } /* Some sources do not like this case. While we don't allow it to be entered directly, * it can occur when raising a null node. * TODO: support rewrites of the ordering/entire window function expression */ OrderBy orderBy = windowFunction.getWindowSpecification().getOrderBy(); if (orderBy != null) { for (OrderByItem item : orderBy.getOrderByItems()) { if (EvaluatableVisitor.willBecomeConstant(SymbolMap.getExpression(item.getSymbol()))) { markInvalid(windowFunction, "Window function order by constant not supported."); //$NON-NLS-1$ return; } } } try { if (!CapabilitiesUtil.checkElementsAreSearchable(windowFunction.getWindowSpecification().getPartition(), metadata, SupportConstants.Element.SEARCHABLE_COMPARE)) { markInvalid(windowFunction, "not all source columns support search type"); //$NON-NLS-1$ } } catch(QueryMetadataException e) { handleException(new TeiidComponentException(e)); } catch(TeiidComponentException e) { handleException(e); } } @Override public void visit(OrderByItem obj) { try { checkElementsAreSearchable(obj.getSymbol(), SupportConstants.Element.SEARCHABLE_COMPARE); if (!CapabilitiesUtil.supportsNullOrdering(this.metadata, this.capFinder, this.modelID, obj)) { markInvalid(obj, "Desired null ordering is not supported by source"); //$NON-NLS-1$ } } catch(QueryMetadataException e) { handleException(new TeiidComponentException(e)); } catch(TeiidComponentException e) { handleException(e); } } @Override public void visit(OrderBy obj) { String collation = null; try { collation = (String) CapabilitiesUtil.getProperty(Capability.COLLATION_LOCALE, modelID, metadata, capFinder); } catch(QueryMetadataException e) { handleException(new TeiidComponentException(e)); } catch(TeiidComponentException e) { handleException(e); } CommandContext commandContext = CommandContext.getThreadLocalContext(); if (collation != null && commandContext != null && commandContext.getOptions().isRequireTeiidCollation() && !collation.equals(DataTypeManager.COLLATION_LOCALE)) { for (OrderByItem symbol : obj.getOrderByItems()) { if (symbol.getSymbol().getType() == DataTypeManager.DefaultDataClasses.STRING || symbol.getSymbol().getType() == DataTypeManager.DefaultDataClasses.CLOB || symbol.getSymbol().getType() == DataTypeManager.DefaultDataClasses.CHAR) { //we require the collation to match markInvalid(obj, "source is not using the same collation as Teiid"); //$NON-NLS-1$ break; } } } } public void visit(CaseExpression obj) { if(! this.caps.supportsCapability(Capability.QUERY_CASE) && !willBecomeConstant(obj)) { markInvalid(obj, "CaseExpression pushdown not supported by source"); //$NON-NLS-1$ } } public void visit(CompareCriteria obj) { checkCompareCriteria(obj, obj.getRightExpression()); checkLiteralComparison(obj, Arrays.asList(obj.getRightExpression())); } private void checkLiteralComparison(LanguageObject obj, Collection<? extends LanguageObject> toCheck) { if (isJoin || !this.caps.supportsCapability(Capability.CRITERIA_ONLY_LITERAL_COMPARE)) { return; } for (LanguageObject languageObject : toCheck) { if (!EvaluatableVisitor.willBecomeConstant(languageObject)) { markInvalid(obj, "Non-literal comparison not supported by source."); //$NON-NLS-1$ return; } } } public void checkCompareCriteria(AbstractCompareCriteria obj, Expression rightExpression) { boolean negated = false; // Check if operation is allowed Capability operatorCap = null; switch(obj.getOperator()) { case CompareCriteria.NE: negated = true; case CompareCriteria.EQ: operatorCap = Capability.CRITERIA_COMPARE_EQ; break; case CompareCriteria.LT: case CompareCriteria.GT: operatorCap = Capability.CRITERIA_COMPARE_ORDERED_EXCLUSIVE; break; case CompareCriteria.LE: case CompareCriteria.GE: operatorCap = Capability.CRITERIA_COMPARE_ORDERED; break; } // Check if compares are allowed if(! this.caps.supportsCapability(operatorCap)) { boolean unsupported = true; if (operatorCap == Capability.CRITERIA_COMPARE_ORDERED_EXCLUSIVE && this.caps.supportsCapability(Capability.CRITERIA_COMPARE_ORDERED) && this.caps.supportsCapability(Capability.CRITERIA_NOT)) { unsupported = false; } if (unsupported) { markInvalid(obj, operatorCap + " CompareCriteria not supported by source"); //$NON-NLS-1$ return; } } if (negated && !this.caps.supportsCapability(Capability.CRITERIA_NOT)) { markInvalid(obj, "Negation is not supported by source"); //$NON-NLS-1$ return; } // Check capabilities of the elements try { int support = SupportConstants.Element.SEARCHABLE_COMPARE; if (!negated && obj.getOperator() == CompareCriteria.EQ) { support = SupportConstants.Element.SEARCHABLE_EQUALITY; } checkElementsAreSearchable(obj.getLeftExpression(), support); checkElementsAreSearchable(rightExpression, support); } catch(QueryMetadataException e) { handleException(new TeiidComponentException(e)); } catch(TeiidComponentException e) { handleException(e); } } public void visit(CompoundCriteria crit) { int operator = crit.getOperator(); // Verify capabilities are supported if(operator == CompoundCriteria.OR && !this.caps.supportsCapability(Capability.CRITERIA_OR) && !willBecomeConstant(crit)) { markInvalid(crit, "OR criteria not supported by source"); //$NON-NLS-1$ } } static TreeSet<String> parseFormat = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER); static { parseFormat.add(SourceSystemFunctions.PARSEBIGDECIMAL); parseFormat.add(SourceSystemFunctions.FORMATBIGDECIMAL); parseFormat.add(SourceSystemFunctions.PARSETIMESTAMP); parseFormat.add(SourceSystemFunctions.FORMATTIMESTAMP); } public void visit(Function obj) { try { //if the function can be evaluated then return as it will get replaced during the final rewrite if (willBecomeConstant(obj)) { return; } if(obj.getFunctionDescriptor().getPushdown() == PushDown.CANNOT_PUSHDOWN) { markInvalid(obj, "Function metadata indicates it cannot be pusheddown."); //$NON-NLS-1$ return; } if (! CapabilitiesUtil.supportsScalarFunction(modelID, obj, metadata, capFinder)) { markInvalid(obj, (obj.isImplicit()?"(implicit) ":"") + obj.getName() + " function not supported by source"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ return; } String name = obj.getName(); if (CapabilitiesUtil.supports(Capability.ONLY_FORMAT_LITERALS, modelID, metadata, capFinder) && parseFormat.contains(name)) { if (!(obj.getArg(1) instanceof Constant)) { markInvalid(obj, obj.getName() + " non-literal parse format function not supported by source"); //$NON-NLS-1$ return; } Constant c = (Constant)obj.getArg(1); if (c.isMultiValued()) { markInvalid(obj, obj.getName() + " non-literal parse format function not supported by source"); //$NON-NLS-1$ return; } if (!CapabilitiesUtil.getCapabilities(modelID, metadata, capFinder).supportsFormatLiteral((String)c.getValue(), name.endsWith(DataTypeManager.DefaultDataTypes.TIMESTAMP)?Format.DATE:Format.NUMBER)) { markInvalid(obj, obj.getName() + " literal parse " + c + " not supported by source"); //$NON-NLS-1$ //$NON-NLS-2$ return; } c.setBindEligible(false); } } catch(QueryMetadataException e) { handleException(new TeiidComponentException(e)); } catch(TeiidComponentException e) { handleException(e); } } public void visit(IsNullCriteria obj) { // Check if compares are allowed if (willBecomeConstant(obj)) { return; } if(! this.caps.supportsCapability(Capability.CRITERIA_ISNULL)) { markInvalid(obj, "IsNull not supported by source"); //$NON-NLS-1$ return; } if (obj.isNegated() && !this.caps.supportsCapability(Capability.CRITERIA_NOT)) { markInvalid(obj, "Negation is not supported by source"); //$NON-NLS-1$ return; } } public void visit(MatchCriteria obj) { if (willBecomeConstant(obj)) { return; } switch (obj.getMode()) { case LIKE: if(! this.caps.supportsCapability(Capability.CRITERIA_LIKE)) { markInvalid(obj, "Like is not supported by source"); //$NON-NLS-1$ return; } break; case SIMILAR: if(! this.caps.supportsCapability(Capability.CRITERIA_SIMILAR)) { markInvalid(obj, "Similar to is not supported by source"); //$NON-NLS-1$ return; } break; case REGEX: if(! this.caps.supportsCapability(Capability.CRITERIA_LIKE_REGEX)) { markInvalid(obj, "Like_regex is not supported by source"); //$NON-NLS-1$ return; } break; } // Check ESCAPE char if necessary if(obj.getEscapeChar() != MatchCriteria.NULL_ESCAPE_CHAR && ! this.caps.supportsCapability(Capability.CRITERIA_LIKE_ESCAPE)) { markInvalid(obj, "Like escape is not supported by source"); //$NON-NLS-1$ return; } Character required = (Character) caps.getSourceProperty(Capability.REQUIRED_LIKE_ESCAPE); if (required != null && obj.getEscapeChar() != MatchCriteria.NULL_ESCAPE_CHAR && !required.equals(obj.getEscapeChar())) { markInvalid(obj, "Escape " + obj.getEscapeChar() + " is not supported by source. Escape " + required + " is required"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ return; } //check NOT if(obj.isNegated() && ! this.caps.supportsCapability(Capability.CRITERIA_NOT)) { markInvalid(obj, "Negation is not supported by source"); //$NON-NLS-1$ return; } // Check capabilities of the elements try { checkElementsAreSearchable(obj.getLeftExpression(), SupportConstants.Element.SEARCHABLE_LIKE); } catch(QueryMetadataException e) { handleException(new TeiidComponentException(e)); } catch(TeiidComponentException e) { handleException(e); } checkLiteralComparison(obj, Arrays.asList(obj.getRightExpression())); } public void visit(NotCriteria obj) { // Check if compares are allowed if(! this.caps.supportsCapability(Capability.CRITERIA_NOT) && !willBecomeConstant(obj)) { markInvalid(obj, "Negation is not supported by source"); //$NON-NLS-1$ return; } } public void visit(SearchedCaseExpression obj) { if(! this.caps.supportsCapability(Capability.QUERY_SEARCHED_CASE) && !willBecomeConstant(obj)) { markInvalid(obj, "SearchedCase is not supported by source"); //$NON-NLS-1$ } } public void visit(SetCriteria crit) { if (willBecomeConstant(crit)) { return; } checkAbstractSetCriteria(crit); try { int maxSize = CapabilitiesUtil.getMaxInCriteriaSize(modelID, metadata, capFinder); int maxPredicates = CapabilitiesUtil.getMaxDependentPredicates(modelID, metadata, capFinder); //allow 1/2 to a single predicate - TODO: make this more precise if (maxSize > 0 && maxPredicates > 0 && crit.getValues().size() > Math.max(maxSize, (maxSize * (long)maxPredicates)/2)) { markInvalid(crit, "SetCriteria size exceeds maximum for source"); //$NON-NLS-1$ return; } } catch(QueryMetadataException e) { handleException(new TeiidComponentException(e)); } catch(TeiidComponentException e) { handleException(e); } checkLiteralComparison(crit, crit.getValues()); } /** * @see org.teiid.query.sql.LanguageVisitor#visit(org.teiid.query.sql.lang.ExistsCriteria) */ public void visit(ExistsCriteria crit) { // Check if exists criteria are allowed if(! this.caps.supportsCapability(Capability.CRITERIA_EXISTS)) { markInvalid(crit, "Exists is not supported by source"); //$NON-NLS-1$ return; } if (crit.isNegated() && !this.caps.supportsCapability(Capability.CRITERIA_NOT)) { markInvalid(crit, "Negation is not supported by source"); //$NON-NLS-1$ return; } try { if (validateSubqueryPushdown(crit, modelID, metadata, capFinder, analysisRecord) == null) { if (crit.getCommand().getCorrelatedReferences() == null) { crit.setShouldEvaluate(true); } else { markInvalid(crit.getCommand(), "Subquery cannot be pushed down"); //$NON-NLS-1$ } } } catch (TeiidComponentException e) { handleException(e); } } /** * @see org.teiid.query.sql.LanguageVisitor#visit(org.teiid.query.sql.lang.SubqueryCompareCriteria) */ public void visit(SubqueryCompareCriteria crit) { if (crit.getArrayExpression() != null) { markInvalid(crit, "Quantified compare with an array cannot yet be pushed down."); //$NON-NLS-1$ return; } // Check if quantification operator is allowed Capability capability = Capability.QUERY_SUBQUERIES_SCALAR; switch(crit.getPredicateQuantifier()) { case SubqueryCompareCriteria.ALL: capability = Capability.CRITERIA_QUANTIFIED_ALL; break; case SubqueryCompareCriteria.ANY: capability = Capability.CRITERIA_QUANTIFIED_SOME; break; case SubqueryCompareCriteria.SOME: capability = Capability.CRITERIA_QUANTIFIED_SOME; break; } if(! this.caps.supportsCapability(capability)) { markInvalid(crit, "SubqueryCompare not supported by source"); //$NON-NLS-1$ return; } checkCompareCriteria(crit, crit.getCommand().getProjectedSymbols().get(0)); // Check capabilities of the elements try { if (validateSubqueryPushdown(crit, modelID, metadata, capFinder, analysisRecord) == null) { markInvalid(crit.getCommand(), "Subquery cannot be pushed down"); //$NON-NLS-1$ } } catch(QueryMetadataException e) { handleException(new TeiidComponentException(e)); } catch(TeiidComponentException e) { handleException(e); } } @Override public void visit(ScalarSubquery obj) { try { if (obj.shouldEvaluate()) { return; } boolean canPreEval = obj.getCommand().getCorrelatedReferences() == null; if(!this.caps.supportsCapability(Capability.QUERY_SUBQUERIES_SCALAR)) { markEvaluatable(obj, canPreEval, "Correlated/nonDeterministic ScalarSubquery is not supported"); //$NON-NLS-1$ } else if (validateSubqueryPushdown(obj, modelID, metadata, capFinder, analysisRecord) == null) { markEvaluatable(obj, canPreEval, "Subquery cannot be pushed down"); //$NON-NLS-1$ } else if (this.isSelectClause && !this.caps.supportsCapability(Capability.QUERY_SUBQUERIES_SCALAR_PROJECTION)) { markEvaluatable(obj, canPreEval, "Correlated/nonDeterministic ScalarSubquery cannot be used in the SELECT clause"); //$NON-NLS-1$ } else if (canPreEval) { //preserve the prior behavior of always pre-evaluating against a with temp group Collection<GroupSymbol> groups = GroupCollectorVisitor.getGroupsIgnoreInlineViews(obj.getCommand(), true); boolean allTemp = true; for (GroupSymbol gs : groups) { if (!gs.isPushedCommonTable()) { allTemp = false; break; } } if (allTemp) { obj.setShouldEvaluate(true); } } } catch(QueryMetadataException e) { handleException(new TeiidComponentException(e)); } catch(TeiidComponentException e) { handleException(e); } } private void markEvaluatable(ScalarSubquery obj, boolean canPreEval, String reasonWhyInvalid) { if (canPreEval) { obj.setShouldEvaluate(true); } else { markInvalid(obj.getCommand(), reasonWhyInvalid); } } public void visit(SubquerySetCriteria crit) { checkAbstractSetCriteria(crit); try { // Check if compares with subqueries are allowed if(! this.caps.supportsCapability(Capability.CRITERIA_IN_SUBQUERY)) { markInvalid(crit, "SubqueryIn is not supported by source"); //$NON-NLS-1$ return; } if (validateSubqueryPushdown(crit, modelID, metadata, capFinder, analysisRecord) == null) { markInvalid(crit.getCommand(), "Subquery cannot be pushed down"); //$NON-NLS-1$ } } catch(QueryMetadataException e) { handleException(new TeiidComponentException(e)); } catch(TeiidComponentException e) { handleException(e); } } public void checkAbstractSetCriteria(AbstractSetCriteria crit) { try { // Check if compares are allowed if(! this.caps.supportsCapability(Capability.CRITERIA_IN)) { markInvalid(crit, "In is not supported by source"); //$NON-NLS-1$ return; } if (crit.isNegated() && !this.caps.supportsCapability(Capability.CRITERIA_NOT)) { markInvalid(crit, "Negation is not supported by source"); //$NON-NLS-1$ return; } // Check capabilities of the elements checkElementsAreSearchable(crit.getExpression(), SupportConstants.Element.SEARCHABLE_COMPARE); } catch(QueryMetadataException e) { handleException(new TeiidComponentException(e)); } catch(TeiidComponentException e) { handleException(e); } } public void visit(DependentSetCriteria crit) { checkAbstractSetCriteria(crit); } private void checkElementsAreSearchable(LanguageObject crit, int searchableType) throws QueryMetadataException, TeiidComponentException { if (!CapabilitiesUtil.checkElementsAreSearchable(Arrays.asList(crit), metadata, searchableType)) { markInvalid(crit, "not all source columns support search type"); //$NON-NLS-1$ } } /** * Return null if the subquery cannot be pushed down, otherwise the model * id of the pushdown target. * @param subqueryContainer * @param critNodeModelID * @param metadata * @param capFinder * @return * @throws TeiidComponentException */ public static Object validateSubqueryPushdown(SubqueryContainer<?> subqueryContainer, Object critNodeModelID, QueryMetadataInterface metadata, CapabilitiesFinder capFinder, AnalysisRecord analysisRecord) throws TeiidComponentException { ProcessorPlan plan = subqueryContainer.getCommand().getProcessorPlan(); if (plan != null) { AccessNode aNode = getAccessNode(plan); if (aNode == null) { return null; } critNodeModelID = validateCommandPushdown(critNodeModelID, metadata, capFinder, aNode, true); } if (critNodeModelID == null) { return null; } // Check whether source supports correlated subqueries and if not, whether criteria has them SymbolMap refs = subqueryContainer.getCommand().getCorrelatedReferences(); try { if(refs != null && !refs.asMap().isEmpty()) { if(! CapabilitiesUtil.supports(Capability.QUERY_SUBQUERIES_CORRELATED, critNodeModelID, metadata, capFinder)) { return null; } if (!CapabilitiesUtil.supports(Capability.SUBQUERY_CORRELATED_LIMIT, critNodeModelID, metadata, capFinder)) { QueryCommand command = (QueryCommand)subqueryContainer.getCommand(); if (command.getLimit() != null && !command.getLimit().isImplicit()) { return null; } } //TODO: this check sees as correlated references as coming from the containing scope //but this is only an issue with deeply nested subqueries if (!CriteriaCapabilityValidatorVisitor.canPushLanguageObject(subqueryContainer.getCommand(), critNodeModelID, metadata, capFinder, analysisRecord )) { return null; } } else if (CapabilitiesUtil.supports(Capability.QUERY_SUBQUERIES_ONLY_CORRELATED, critNodeModelID, metadata, capFinder)) { return null; } } catch(QueryMetadataException e) { throw new TeiidComponentException(QueryPlugin.Event.TEIID30271, e); } if (!CapabilitiesUtil.supports(Capability.SUBQUERY_COMMON_TABLE_EXPRESSIONS, critNodeModelID, metadata, capFinder) && subqueryContainer.getCommand() instanceof QueryCommand) { QueryCommand command = (QueryCommand)subqueryContainer.getCommand(); if (command.getWith() != null) { return null; } } // Found no reason why this node is not eligible return critNodeModelID; } public static Object validateCommandPushdown(Object critNodeModelID, QueryMetadataInterface metadata, CapabilitiesFinder capFinder, AccessNode aNode, boolean considerConformed) throws TeiidComponentException { // Check that query in access node is for the same model as current node try { if (!(aNode.getCommand() instanceof QueryCommand)) { return null; } Object modelID = aNode.getModelId(); if (critNodeModelID == null) { critNodeModelID = modelID; } else if(!CapabilitiesUtil.isSameConnector(critNodeModelID, modelID, metadata, capFinder) && (!considerConformed || !RuleRaiseAccess.isConformed(metadata, capFinder, aNode.getConformedTo(), modelID, null, critNodeModelID))) { return null; } } catch(QueryMetadataException e) { throw new TeiidComponentException(QueryPlugin.Event.TEIID30272, e, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30272)); } return critNodeModelID; } public static AccessNode getAccessNode(ProcessorPlan plan) { if(!(plan instanceof RelationalPlan)) { return null; } RelationalPlan rplan = (RelationalPlan) plan; // Check that the plan is just an access node RelationalNode accessNode = rplan.getRootNode(); if (accessNode instanceof LimitNode) { LimitNode ln = (LimitNode)accessNode; if (!ln.isImplicit()) { return null; } accessNode = ln.getChildren()[0]; } if (! (accessNode instanceof AccessNode)) { return null; } return (AccessNode)accessNode; } public static QueryCommand getQueryCommand(AccessNode aNode) { if (aNode == null) { return null; } Command command = aNode.getCommand(); if(!(command instanceof QueryCommand)) { return null; } QueryCommand queryCommand = (QueryCommand)command; if (aNode.getProjection() != null && aNode.getProjection().length > 0) { Query newCommand = (Query)queryCommand.clone(); newCommand.getSelect().setSymbols(aNode.getOriginalSelect()); return newCommand; } return queryCommand; } private void handleException(TeiidComponentException e) { this.valid = false; this.exception = e; setAbort(true); } public TeiidComponentException getException() { return this.exception; } private void markInvalid(LanguageObject object, String reason) { this.valid = false; setAbort(true); if (analysisRecord != null && analysisRecord.recordAnnotations()) { try { analysisRecord.addAnnotation(Annotation.RELATIONAL_PLANNER, reason + " " + this.metadata.getName(this.modelID), object + " was not pushed", Priority.LOW); //$NON-NLS-1$ //$NON-NLS-2$ } catch (QueryMetadataException e) { } catch (TeiidComponentException e) { } } } public boolean isValid() { return this.valid; } public static boolean canPushLanguageObject(LanguageObject obj, Object modelID, QueryMetadataInterface metadata, CapabilitiesFinder capFinder, AnalysisRecord analysisRecord) throws QueryMetadataException, TeiidComponentException { return canPushLanguageObject(obj, modelID, metadata, capFinder, analysisRecord, false, false); } public static boolean canPushLanguageObject(LanguageObject obj, Object modelID, final QueryMetadataInterface metadata, CapabilitiesFinder capFinder, AnalysisRecord analysisRecord, boolean isJoin, boolean isSelectClause) throws QueryMetadataException, TeiidComponentException { if(obj == null) { return true; } if(modelID == null || metadata.isVirtualModel(modelID)) { // Couldn't determine model ID, so give up return false; } String modelName = metadata.getFullName(modelID); SourceCapabilities caps = capFinder.findCapabilities(modelName); if (caps == null) { return true; //this doesn't seem right, but tests were expecting it... } CriteriaCapabilityValidatorVisitor visitor = new CriteriaCapabilityValidatorVisitor(modelID, metadata, capFinder, caps); visitor.analysisRecord = analysisRecord; visitor.isJoin = isJoin; visitor.isSelectClause = isSelectClause; //we use an array to represent multiple comparision attributes, //but we don't want that to inhibit pushdown as we'll account for that later //in criteria processing final EvaluatableVisitor ev = new EvaluatableVisitor(modelID, metadata, capFinder); PreOrPostOrderNavigator nav = new PreOrPostOrderNavigator(visitor, PreOrPostOrderNavigator.POST_ORDER, false) { @Override public void visit(DependentSetCriteria obj1) { if (obj1.hasMultipleAttributes()) { Array array = (Array) obj1.getExpression(); visitNodes(array.getExpressions()); super.postVisitVisitor(obj1); } else { super.visit(obj1); } } @Override protected void visitNode(LanguageObject obj) { if (obj == null) { return; } Determinism d = ev.getDeterminismLevel(); boolean pushDown = ev.requiresEvaluation(EvaluationLevel.PUSH_DOWN); //decend with clean state, then restore ev.reset(); super.visitNode(obj); ev.setDeterminismLevel(d); if (pushDown) { ev.evaluationNotPossible(EvaluationLevel.PUSH_DOWN); } } @Override protected void visitVisitor(LanguageObject obj) { if (obj == null) { return; } if (!ev.requiresEvaluation(EvaluationLevel.PUSH_DOWN) && ev.getDeterminismLevel() != Determinism.NONDETERMINISTIC) { if (obj instanceof ElementSymbol) { ElementSymbol es = (ElementSymbol)obj; if (es.getMetadataID() != null) { try { if (metadata.isMultiSourceElement(es.getMetadataID())) { return; //no need to visit } } catch (QueryMetadataException e) { } catch (TeiidComponentException e) { } } } obj.acceptVisitor(ev); if (obj instanceof Expression) { if (obj instanceof Function) { if (!(obj instanceof AggregateSymbol)) { Function f = (Function)obj; if (f.getFunctionDescriptor().getPushdown() != PushDown.MUST_PUSHDOWN && f.getFunctionDescriptor().getDeterministic() != Determinism.NONDETERMINISTIC) { return; //don't need to consider } } } else if (obj instanceof Criteria && !(obj instanceof SubqueryContainer) && !(obj instanceof DependentSetCriteria)) { return; //don't need to consider } } } super.visitVisitor(obj); } }; obj.acceptVisitor(nav); if(visitor.getException() != null) { throw visitor.getException(); } return visitor.isValid(); } public void setCheckEvaluation(boolean b) { this.checkEvaluation = b; } }