/* * 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.xml; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import org.teiid.api.exception.query.QueryMetadataException; import org.teiid.api.exception.query.QueryPlannerException; import org.teiid.core.TeiidComponentException; import org.teiid.query.QueryPlugin; import org.teiid.query.function.FunctionLibrary; import org.teiid.query.mapping.xml.MappingDocument; import org.teiid.query.mapping.xml.MappingNode; import org.teiid.query.mapping.xml.MappingSourceNode; import org.teiid.query.mapping.xml.ResultSetInfo; import org.teiid.query.metadata.QueryMetadataInterface; import org.teiid.query.sql.lang.CompareCriteria; import org.teiid.query.sql.lang.Criteria; import org.teiid.query.sql.symbol.Constant; import org.teiid.query.sql.symbol.ElementSymbol; import org.teiid.query.sql.symbol.Function; import org.teiid.query.sql.symbol.GroupSymbol; import org.teiid.query.sql.symbol.Symbol; import org.teiid.query.sql.visitor.ElementCollectorVisitor; public class CriteriaPlanner { /** * Take the criteria from the user's command and break it into pieces applicable to each * mapping class result set in the mapping document * * Assumes that criteria is in CNF where each conjunct applies to only a single context. * * The post condition of this method is that each result set with * * @param criteria Criteria from user's command * @throws QueryPlannerException for any logical exception detected during planning * @throws QueryMetadataException if metadata encounters exception * @throws TeiidComponentException unexpected exception */ static void placeUserCriteria(Criteria criteria, XMLPlannerEnvironment planEnv) throws QueryPlannerException, QueryMetadataException, TeiidComponentException { for (Iterator<Criteria> conjunctIter = Criteria.separateCriteriaByAnd(criteria).iterator(); conjunctIter.hasNext();) { Criteria conjunct = conjunctIter.next(); if (planStagingTableCriteria(conjunct, planEnv)) { continue; } //this is a gross hack, these should not be criteria if (planRowLimitFunction(conjunct, criteria, planEnv)) { continue; } MappingNode context = null; Collection<Function> contextFunctions = ContextReplacerVisitor.replaceContextFunctions(conjunct); if (!contextFunctions.isEmpty()) { //ensure that every part of the conjunct is to the same context for (Function contextFunction : contextFunctions) { MappingNode otherContext = getContext(planEnv, contextFunction); if (context == null) { context = otherContext; } else if (context != otherContext){ throw new QueryPlannerException(QueryPlugin.Event.TEIID30300, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30300, criteria)); } } //search up to find the source node MappingNode contextRsNode = context.getSourceNode(); if (contextRsNode != null) { context = contextRsNode; } } else { context = planEnv.mappingDoc; } Set<MappingSourceNode> sourceNodes = collectSourceNodesInConjunct(conjunct, context, planEnv.mappingDoc); //TODO: this can be replaced with method on the source node? MappingSourceNode criteriaRs = findRootResultSetNode(context, sourceNodes, criteria); ResultSetInfo rs = criteriaRs.getResultSetInfo(); Criteria convertedCrit = XMLNodeMappingVisitor.convertCriteria(conjunct, planEnv.mappingDoc, planEnv.getGlobalMetadata()); rs.setCriteria(Criteria.combineCriteria(rs.getCriteria(), convertedCrit)); rs.addToCriteriaResultSets(sourceNodes); } } /** * This method collects all the MappingSourceNode(s) at or below the context given. */ private static Set<MappingSourceNode> collectSourceNodesInConjunct(Criteria conjunct, MappingNode context, MappingDocument mappingDoc) throws QueryPlannerException { Collection<ElementSymbol> elements = ElementCollectorVisitor.getElements(conjunct, true); Set<MappingSourceNode> resultSets = new HashSet<MappingSourceNode>(); String contextFullName = context.getFullyQualifiedName(); //validate that each element's group is under the current context or is in the direct parentage for (ElementSymbol elementSymbol : elements) { String elementFullName = elementSymbol.getName(); MappingNode node = MappingNode.findNode(mappingDoc, elementFullName); MappingSourceNode elementRsNode = node.getSourceNode(); if (elementRsNode == null) { throw new QueryPlannerException(QueryPlugin.Event.TEIID30301, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30301, elementSymbol)); } String elementRsFullName = elementRsNode.getFullyQualifiedName(); //check for a match at or below the context if (contextFullName.equals(elementRsFullName) || elementRsFullName.startsWith(contextFullName + Symbol.SEPARATOR)) { resultSets.add(elementRsNode); continue; } //check for match above the context if (contextFullName.startsWith(elementRsFullName + Symbol.SEPARATOR)) { continue; } throw new QueryPlannerException(QueryPlugin.Event.TEIID30302, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30302, elementSymbol, context.getFullyQualifiedName())); } return resultSets; } private static MappingSourceNode findRootResultSetNode(MappingNode context, Set<MappingSourceNode> resultSets, Criteria criteria) throws QueryPlannerException { if (context instanceof MappingSourceNode) { return (MappingSourceNode)context; } Set<MappingNode> criteriaResultSets = new HashSet<MappingNode>(); // if the context node is not the root node then we need to find the root source node from list. for (MappingNode node : resultSets) { MappingNode root = node; while (node != null) { if (node instanceof MappingSourceNode) { root = node; } node = node.getParent(); } criteriaResultSets.add(root); } if (criteriaResultSets.size() != 1) { //TODO: this assumption could be relaxed if we allow context to be from a document perspective, rather than from a result set throw new QueryPlannerException(QueryPlugin.Event.TEIID30303, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30303, criteria)); } return (MappingSourceNode)criteriaResultSets.iterator().next(); } /** * Removes non-inferred staging table criteria. Places it directly onto the contextCriteria */ static boolean planStagingTableCriteria(Criteria criteria, XMLPlannerEnvironment planEnv) throws QueryPlannerException, QueryMetadataException, TeiidComponentException { String rootTempGroupName = getStagingTableForConjunct(criteria, planEnv.getGlobalMetadata()); if (rootTempGroupName == null){ return false; } // Defect 13172 - be careful to check for previously found root staging table // conjuncts and combine that with current conjunct ResultSetInfo rs = planEnv.getStagingTableResultsInfo(rootTempGroupName); rs.setCriteria(Criteria.combineCriteria(rs.getCriteria(), criteria)); return true; } static boolean planRowLimitFunction(Criteria conjunct, Criteria wholeCrit, XMLPlannerEnvironment planEnv) throws QueryPlannerException, QueryMetadataException, TeiidComponentException { // Check for "rowlimit" or "rowlimitexception" pseudo-function: // Restrictions // -Single arg must be any xml doc node that is within the scope of a mapping class // -Can't have conflicting row limits on the same mapping class // (Query Validator enforces additional restrictions.) Function rowLimitFunction = null; Constant rowLimitConstant = null; boolean exceptionOnRowLimit = false; if (conjunct instanceof CompareCriteria) { CompareCriteria crit = (CompareCriteria)conjunct; if (crit.getLeftExpression() instanceof Function) { Function function = (Function)crit.getLeftExpression(); if (function.getName().equalsIgnoreCase(FunctionLibrary.ROWLIMIT)) { rowLimitFunction = function; rowLimitConstant = (Constant)crit.getRightExpression(); } else if (function.getName().equalsIgnoreCase(FunctionLibrary.ROWLIMITEXCEPTION)) { rowLimitFunction = function; rowLimitConstant = (Constant)crit.getRightExpression(); exceptionOnRowLimit = true; } } if (rowLimitFunction == null && crit.getRightExpression() instanceof Function) { Function function = (Function)crit.getRightExpression(); if (function.getName().equalsIgnoreCase(FunctionLibrary.ROWLIMIT)) { rowLimitFunction = function; rowLimitConstant = (Constant)crit.getLeftExpression(); } else if (function.getName().equalsIgnoreCase(FunctionLibrary.ROWLIMITEXCEPTION)) { rowLimitFunction = function; rowLimitConstant = (Constant)crit.getLeftExpression(); exceptionOnRowLimit = true; } } } if (rowLimitFunction == null) { return false; } int rowLimit = ((Integer)rowLimitConstant.getValue()).intValue(); String fullyQualifiedNodeName = planEnv.getGlobalMetadata().getFullName(((ElementSymbol)rowLimitFunction.getArg(0)).getMetadataID()); MappingNode node = MappingNode.findNode(planEnv.mappingDoc, fullyQualifiedNodeName); MappingSourceNode sourceNode = node.getSourceNode(); if (sourceNode == null) { String msg = QueryPlugin.Util.getString("XMLPlanner.The_rowlimit_parameter_{0}_is_not_in_the_scope_of_any_mapping_class", fullyQualifiedNodeName); //$NON-NLS-1$ throw new QueryPlannerException(QueryPlugin.Event.TEIID30304, msg); } ResultSetInfo criteriaRsInfo = sourceNode.getResultSetInfo(); // Check for conflicting row limits on the same mapping class int existingLimit = criteriaRsInfo.getUserRowLimit(); if (existingLimit > 0 && existingLimit != rowLimit) { String msg = QueryPlugin.Util.getString("XMLPlanner.Criteria_{0}_contains_conflicting_row_limits", wholeCrit); //$NON-NLS-1$ throw new QueryPlannerException(QueryPlugin.Event.TEIID30305, msg); } criteriaRsInfo.setUserRowLimit(rowLimit, exceptionOnRowLimit); // No further processing on this conjunct return true; } /** * Validate that all elements within a conjunct are either referring to a temp table * or NOT referring to a temp table. Can't mix element types within a conjunct. * @param conjunct Conjunct to validate * @return String name of temporary result set, if conjunct is for the root temp table; * or return null if the conjunct is for XML document node(s) * @throws QueryPlannerException if conjunct has mixed types */ static String getStagingTableForConjunct(Criteria conjunct, QueryMetadataInterface metadata) throws QueryPlannerException, QueryMetadataException, TeiidComponentException { Collection<ElementSymbol> elements = ElementCollectorVisitor.getElements(conjunct, true); boolean first = true; String resultSet = null; // Check each remaining element to make sure it matches for (ElementSymbol element : elements) { GroupSymbol group = element.getGroupSymbol(); //assumes that all non-xml group elements are temp elements boolean hasTempElement = !metadata.isXMLGroup(group.getMetadataID()); if(!first && hasTempElement && resultSet == null) { throw new QueryPlannerException(QueryPlugin.Event.TEIID30306, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30306, conjunct)); } if (hasTempElement) { String currentResultSet = metadata.getFullName(element.getGroupSymbol().getMetadataID()); if (resultSet != null && !resultSet.equalsIgnoreCase(currentResultSet)) { throw new QueryPlannerException(QueryPlugin.Event.TEIID30307, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30307, conjunct)); } resultSet = currentResultSet; } first = false; } if (resultSet != null) { Collection<Function> functions = ContextReplacerVisitor.replaceContextFunctions(conjunct); if (!functions.isEmpty()) { throw new QueryPlannerException(QueryPlugin.Event.TEIID30308, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30308)); } //should also throw an exception if it contains a row limit function } return resultSet; } /** * Returns the context for a given context function */ static MappingNode getContext(XMLPlannerEnvironment planEnv, Function contextFunction) throws QueryPlannerException { ElementSymbol targetContext = (ElementSymbol)contextFunction.getArg(0); MappingNode contextNode = MappingNode.findNode(planEnv.mappingDoc, targetContext.getName()); if (contextNode == null){ throw new QueryPlannerException(QueryPlugin.Event.TEIID30309, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30309, targetContext)); } return contextNode; } }