/*
* 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;
}
}