/*
* 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.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import org.teiid.api.exception.query.QueryMetadataException;
import org.teiid.api.exception.query.QueryParserException;
import org.teiid.api.exception.query.QueryPlannerException;
import org.teiid.api.exception.query.QueryResolverException;
import org.teiid.core.TeiidComponentException;
import org.teiid.core.TeiidRuntimeException;
import org.teiid.logging.LogConstants;
import org.teiid.logging.LogManager;
import org.teiid.query.mapping.relational.QueryNode;
import org.teiid.query.mapping.xml.MappingBaseNode;
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.MappingVisitor;
import org.teiid.query.mapping.xml.Navigator;
import org.teiid.query.mapping.xml.ResultSetInfo;
import org.teiid.query.metadata.TempMetadataStore;
import org.teiid.query.optimizer.relational.RelationalPlanner;
import org.teiid.query.parser.QueryParser;
import org.teiid.query.resolver.QueryResolver;
import org.teiid.query.resolver.util.ResolverUtil;
import org.teiid.query.resolver.util.ResolverVisitor;
import org.teiid.query.sql.lang.*;
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.visitor.ExpressionMappingVisitor;
import org.teiid.query.sql.visitor.GroupCollectorVisitor;
/**
* This class has code to with planning the automatic XML staging queries.
*/
public class XMLStagaingQueryPlanner {
static void stageQueries(MappingDocument doc, final XMLPlannerEnvironment planEnv)
throws QueryPlannerException, QueryMetadataException, TeiidComponentException {
MappingVisitor queryPlanVisitor = new MappingVisitor() {
public void visit(MappingSourceNode sourceNode) {
try {
stagePlannedQuery(sourceNode, planEnv);
} catch (Exception e) {
throw new TeiidRuntimeException(e);
}
}
};
planWalk(doc, queryPlanVisitor);
}
private static void planWalk(MappingDocument doc, MappingVisitor visitor)
throws QueryPlannerException, QueryMetadataException, TeiidComponentException {
try {
Navigator walker = new Navigator(true, visitor);
doc.acceptVisitor(walker);
} catch (TeiidRuntimeException e) {
if (e.getCause() instanceof QueryPlannerException) {
throw (QueryPlannerException)e.getCause();
}
else if (e.getCause() instanceof QueryMetadataException) {
throw (QueryMetadataException)e.getCause();
}
else if (e.getCause() instanceof TeiidComponentException) {
throw (TeiidComponentException)e.getCause();
}
else {
throw e;
}
}
}
static boolean stagePlannedQuery(MappingSourceNode sourceNode, XMLPlannerEnvironment planEnv)
throws QueryPlannerException, QueryMetadataException, TeiidComponentException, QueryResolverException, QueryParserException {
Option option = planEnv.xmlCommand.getOption();
// make sure we do not plan staging table for root mapping class.
if (sourceNode.isRootSourceNode()) {
return false;
}
//TODO: if this source is source for a recursive node, we do not want to stage
if (sourceNode.getAliasResultName() != null) {
return false;
}
String groupName = sourceNode.getActualResultSetName();
ResultSetInfo rsInfo = sourceNode.getResultSetInfo();
// If this node has been eligible for raising, it will be eligible for staging.
if (rsInfo.hasInputSet() && !rsInfo.isCriteriaRaised()) {
return false;
}
// no bindings - no references
QueryNode queryNode = QueryUtil.getQueryNode(groupName, planEnv.getGlobalMetadata());
if (queryNode.getBindings() != null && !queryNode.getBindings().isEmpty()) {
return false;
}
// get the original transformation of the mapping class before planning.
Query stagableQuery = (Query)QueryUtil.getQueryFromQueryNode(groupName, planEnv);
Collection<GroupSymbol> groups = GroupCollectorVisitor.getGroupsIgnoreInlineViews(stagableQuery, false);
//check for already staged queries
if (groups.size() == 1) {
GroupSymbol group = groups.iterator().next();
group = QueryUtil.createResolvedGroup(group.clone(), planEnv.getGlobalMetadata());
if (planEnv.isStagingTable(group.getMetadataID()) && stagableQuery.getCriteria() == null) {
return false;
}
}
Criteria crit = ((Query)rsInfo.getCommand()).getCriteria();
GroupSymbol parent = null;
LinkedHashSet<ElementSymbol> outerReferences = new LinkedHashSet<ElementSymbol>();
LinkedHashSet<ElementSymbol> fkColumns = new LinkedHashSet<ElementSymbol>();
//see if we can perform a dependent join
for (Criteria conjunct : Criteria.separateCriteriaByAnd(crit)) {
if (!(conjunct instanceof CompareCriteria)) {
continue;
}
CompareCriteria cc = (CompareCriteria)conjunct;
if (cc.getOperator() != CompareCriteria.EQ) {
continue;
}
if (!(cc.getLeftExpression() instanceof ElementSymbol)
|| !(cc.getRightExpression() instanceof ElementSymbol)) {
continue;
}
ElementSymbol les = (ElementSymbol)cc.getLeftExpression();
ElementSymbol res = (ElementSymbol)cc.getRightExpression();
if (les.getGroupSymbol().getNonCorrelationName().equalsIgnoreCase(groupName)) {
parent = res.getGroupSymbol();
outerReferences.add(res.clone());
fkColumns.add(les.clone());
} else if (res.getGroupSymbol().getNonCorrelationName().equalsIgnoreCase(groupName)) {
parent = les.getGroupSymbol();
outerReferences.add(les.clone());
fkColumns.add(res.clone());
}
}
String stagingGroupName = planEnv.getStagedResultName(groupName);
boolean recursive = false;
MappingSourceNode msn = sourceNode;
while (!recursive && msn != null) {
MappingNode mappingNode = msn.getChildren().get(0);
if (mappingNode instanceof MappingBaseNode) {
recursive = ((MappingBaseNode)mappingNode).isRootRecursiveNode();
}
msn = msn.getParentSourceNode();
}
if (parent != null && !recursive) {
stagableQuery = (Query)stagableQuery.clone();
String parentName = parent.getNonCorrelationName();
String parentStagingName = planEnv.getStagedResultName(parentName);
GroupSymbol parentTempTable = new GroupSymbol(XMLQueryPlanner.getTempTableName(parentStagingName));
ResultSetInfo parentRsInfo = planEnv.getStagingTableResultsInfo(parentStagingName);
String stagingRoot = sourceNode.getParentSourceNode().getSource();
boolean parentStaged = parentRsInfo.getPlan() != null;
//TODO: check to see if the parent was manually staged
if (!parentStaged) {
//TODO: if not a level 1 child we could use the old auto staging logic instead
//switch the parent over to the source
parentRsInfo = sourceNode.getParentSourceNode().getResultSetInfo();
if (parentRsInfo.getTempTable() == null) {
//create a temp table to represent the resultset
List<Expression> projectedSymbols = parentRsInfo.getCommand().getProjectedSymbols();
ArrayList<Expression> elements = new ArrayList<Expression>(projectedSymbols.size());
for (Expression singleElementSymbol : projectedSymbols) {
singleElementSymbol = (Expression) singleElementSymbol.clone();
ResolverVisitor.resolveLanguageObject(singleElementSymbol, planEnv.getGlobalMetadata());
elements.add(singleElementSymbol);
}
TempMetadataStore store = planEnv.getGlobalMetadata().getMetadataStore();
// create a new group name and to the temp store
GroupSymbol newGroup = new GroupSymbol(SourceNodePlannerVisitor.getNewName("#" + planEnv.getAliasName(parentName) + "_RS", store)); //$NON-NLS-1$ //$NON-NLS-2$
newGroup.setMetadataID(store.addTempGroup(newGroup.getName(), elements, false, true));
parentStagingName = newGroup.getName();
parentTempTable = newGroup;
} else {
parentStagingName = parentRsInfo.getTempTable();
parentTempTable = new GroupSymbol(parentRsInfo.getTempTable());
parentStaged = true;
}
} else {
stagingRoot = parentRsInfo.getStagingRoot();
}
Query query = new Query();
query.setSelect(new Select(Arrays.asList(new ExpressionSymbol("expr", new Constant(1))))); //$NON-NLS-1$
query.setFrom(new From(Arrays.asList(new UnaryFromClause(parentTempTable))));
Map symbolMap = new HashMap();
String inlineViewName = planEnv.getAliasName(rsInfo.getResultSetName());
XMLQueryPlanner.updateSymbolMap(symbolMap, rsInfo.getResultSetName(), inlineViewName, planEnv.getGlobalMetadata());
XMLQueryPlanner.updateSymbolMap(symbolMap, parentName, parentTempTable.getName(), planEnv.getGlobalMetadata());
crit = (Criteria) crit.clone();
ExpressionMappingVisitor.mapExpressions(crit, symbolMap);
if (!stagableQuery.getSelect().isDistinct()) {
query.setHaving(crit);
//group by is added so that subquery planning sees that we are distinct
query.setGroupBy(new GroupBy(new ArrayList<ElementSymbol>(outerReferences)));
ExpressionMappingVisitor.mapExpressions(query.getGroupBy(), symbolMap);
} else {
query.setCriteria(crit);
}
ExistsCriteria ec = new ExistsCriteria();
ec.setSubqueryHint(new ExistsCriteria.SubqueryHint());
ec.getSubqueryHint().setDepJoin();
ec.setCommand(query);
Criteria existing = stagableQuery.getCriteria();
stagableQuery.setCriteria(Criteria.combineCriteria(existing, ec));
if (!XMLQueryPlanner.planStagaingQuery(false, groupName, stagingGroupName, stagableQuery, planEnv)) {
return false;
}
if (!parentStaged) {
//need to associate temp load/get/drop with the rsinfo for use by the execsqlinstruction
Insert insert = new Insert();
insert.setGroup(parentTempTable);
int valCount = parentRsInfo.getCommand().getProjectedSymbols().size();
ArrayList<Reference> vals = new ArrayList<Reference>(valCount);
for (int i = 0; i < valCount; i++) {
vals.add(new Reference(i+1));
}
insert.setValues(vals);
QueryResolver.resolveCommand(insert, planEnv.getGlobalMetadata());
Command tempCommand = QueryParser.getQueryParser().parseCommand("select * from " + parentStagingName); //$NON-NLS-1$
QueryResolver.resolveCommand(tempCommand, planEnv.getGlobalMetadata());
Command dropCommand = QueryParser.getQueryParser().parseCommand("drop table " + parentStagingName); //$NON-NLS-1$
QueryResolver.resolveCommand(dropCommand, planEnv.getGlobalMetadata());
parentRsInfo.setTempTable(parentStagingName);
parentRsInfo.setTempSelect(tempCommand);
parentRsInfo.setTempInsert(insert);
parentRsInfo.setTempDrop(dropCommand);
}
LogManager.logDetail(LogConstants.CTX_XML_PLANNER, "Using a dependent join to load the mapping class", groupName); //$NON-NLS-1$
// add to the document that a staging table has been added
sourceNode.addStagingTable(stagingGroupName);
GroupSymbol tempGroup = new GroupSymbol(XMLQueryPlanner.getTempTableName(stagingGroupName));
ResolverUtil.resolveGroup(tempGroup, planEnv.getGlobalMetadata());
Collection<GroupSymbol> temp = Arrays.asList(tempGroup);
List<ElementSymbol> fk = new ArrayList<ElementSymbol>(fkColumns.size());
for (ElementSymbol elementSymbol : fkColumns) {
ElementSymbol es = new ElementSymbol(elementSymbol.getShortName());
ResolverVisitor.resolveLanguageObject(es, temp, planEnv.getGlobalMetadata());
fk.add(es);
}
ResultSetInfo stagedInfo = planEnv.getStagingTableResultsInfo(stagingGroupName);
stagedInfo.setStagingRoot(stagingRoot);
stagedInfo.setAutoStaged(true);
stagedInfo.addFkColumns(fk);
stagedInfo.setTempTable(tempGroup.getName());
rsInfo.setAutoStaged(true);
return true;
}
//id as mapping class
Object metadataID = planEnv.getGlobalMetadata().getGroupID(sourceNode.getResultName());
if (RelationalPlanner.isNoCacheGroup(planEnv.getGlobalMetadata(), metadataID, option)) {
return false;
}
//id as generated mapping class name
metadataID = planEnv.getGlobalMetadata().getGroupID(sourceNode.getActualResultSetName());
if (RelationalPlanner.isNoCacheGroup(planEnv.getGlobalMetadata(), metadataID, option)) {
return false;
}
stagableQuery = (Query)stagableQuery.clone();
// stage the transformation query if it is successful
if (!XMLQueryPlanner.planStagaingQuery(true, groupName, stagingGroupName, stagableQuery, planEnv)) {
return false;
}
// add to the document that a staging table has been added
sourceNode.addStagingTable(stagingGroupName);
ResultSetInfo stagedInfo = planEnv.getStagingTableResultsInfo(stagingGroupName);
stagedInfo.setAutoStaged(true);
stagedInfo.setTempTable(XMLQueryPlanner.getTempTableName(stagingGroupName));
rsInfo.setAutoStaged(true);
return true;
}
}