/* * 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.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.teiid.api.exception.query.QueryPlannerException; import org.teiid.core.TeiidRuntimeException; import org.teiid.query.QueryPlugin; import org.teiid.query.metadata.TempMetadataID; import org.teiid.query.sql.LanguageVisitor; import org.teiid.query.sql.lang.*; import org.teiid.query.sql.navigator.PreOrderNavigator; import org.teiid.query.sql.symbol.AliasSymbol; import org.teiid.query.sql.symbol.ElementSymbol; import org.teiid.query.sql.symbol.ElementSymbol.DisplayMode; import org.teiid.query.sql.symbol.Expression; import org.teiid.query.sql.symbol.GroupSymbol; import org.teiid.query.sql.symbol.Reference; import org.teiid.query.sql.symbol.ScalarSubquery; import org.teiid.query.sql.symbol.Symbol; import org.teiid.query.sql.util.SymbolMap; /** * Adds safe (generated) aliases to the source command * * The structure is a little convoluted: * AliasGenerator - structure navigator, alters the command by adding alias symbols * NamingVisitor - changes the output names of Element and Group symbols * SQLNamingContext - a hierarchical context for tracking Element and Group names */ public class AliasGenerator extends PreOrderNavigator { private static final String table_prefix = "g_"; //$NON-NLS-1$ private static final String view_prefix = "v_"; //$NON-NLS-1$ private static class NamingVisitor extends LanguageVisitor { private class SQLNamingContext { SQLNamingContext parent; Map<String, Map<String, String>> elementMap = new HashMap<String, Map<String, String>>(); Map<String, String> groupNames = new HashMap<String, String>(); LinkedHashMap<Expression, String> currentSymbols; boolean aliasColumns = false; public SQLNamingContext(SQLNamingContext parent) { this.parent = parent; } public String getElementName(Expression symbol) { if (!(symbol instanceof ElementSymbol)) { return null; } ElementSymbol element = (ElementSymbol)symbol; String newGroupName = this.groupNames.get(element.getGroupSymbol().getName()); if (newGroupName == null) { if (parent == null) { return null; } return parent.getElementName(symbol); } //check for inline view Map<String, String> elements = this.elementMap.get(element.getGroupSymbol().getName()); if (elements != null) { String name = elements.get(element.getShortName()); if (name != null) { renameGroup(element.getGroupSymbol(), newGroupName); return name; } } if (parent != null) { String name = parent.getElementName(symbol); if (name != null) { return name; } } renameGroup(element.getGroupSymbol(), newGroupName); return null; } public void renameGroup(GroupSymbol obj, String newAlias) { if (aliasGroups) { String definition = obj.getNonCorrelationName(); if (newAlias == null) { return; } obj.setName(newAlias); obj.setDefinition(definition); } else if(obj.getDefinition() != null) { obj.setName(obj.getDefinition()); obj.setDefinition(null); } else { obj.setOutputName(null); obj.setOutputDefinition(null); } } private String getGroupName(String group) { String groupName = groupNames.get(group); if (groupName == null) { if (parent == null) { return null; } return parent.getGroupName(group); } return groupName; } } private SQLNamingContext namingContext = new SQLNamingContext(null); boolean aliasGroups; public NamingVisitor(boolean aliasGroups) { this.aliasGroups = aliasGroups; } /** * @see org.teiid.query.sql.LanguageVisitor#visit(org.teiid.query.sql.symbol.ElementSymbol) */ @Override public void visit(ElementSymbol obj) { GroupSymbol group = obj.getGroupSymbol(); if(group == null) { return; } String newName = namingContext.getElementName(obj); if (newName != null) { obj.setShortName(newName); } obj.setDisplayMode(ElementSymbol.DisplayMode.FULLY_QUALIFIED); } /** * @see org.teiid.query.sql.LanguageVisitor#visit(org.teiid.query.sql.symbol.GroupSymbol) */ @Override public void visit(GroupSymbol obj) { this.namingContext.renameGroup(obj, this.namingContext.getGroupName(obj.getName())); } public void createChildNamingContext(boolean aliasColumns) { this.namingContext = new SQLNamingContext(this.namingContext); this.namingContext.aliasColumns = aliasColumns; } public void removeChildNamingContext() { this.namingContext = this.namingContext.parent; } } private NamingVisitor visitor; private int groupIndex; private int viewIndex; private boolean stripColumnAliases; private Map<String, String> aliasMapping; private Collection<String> correlationGroups; public AliasGenerator(boolean aliasGroups) { this(aliasGroups, false); } public AliasGenerator(boolean aliasGroups, boolean stripColumnAliases) { super(new NamingVisitor(aliasGroups)); this.visitor = (NamingVisitor)this.getVisitor(); this.stripColumnAliases = stripColumnAliases; } /** * visit the branches other than the first with individual naming contexts * Aliases are being added in all cases, even though they may only be needed in the order by case. * Adding the same alias to all branches ensures cross db support (db2 in particular) */ public void visit(SetQuery obj) { visitor.createChildNamingContext(true); visitNode(obj.getRightQuery()); visitor.removeChildNamingContext(); visitor.namingContext.aliasColumns = true; visitNode(obj.getLeftQuery()); visitNode(obj.getOrderBy()); } public void visit(Select obj) { List<Expression> selectSymbols = obj.getSymbols(); LinkedHashMap<Expression, String> symbols = new LinkedHashMap<Expression, String>(selectSymbols.size()); for (int i = 0; i < selectSymbols.size(); i++) { Expression symbol = selectSymbols.get(i); visitNode(symbol); boolean needsAlias = visitor.namingContext.aliasColumns; String newAlias = "c_" + i; //$NON-NLS-1$ Expression newSymbol = SymbolMap.getExpression(symbol); if (newSymbol instanceof ElementSymbol) { if (!needsAlias) { newAlias = ((ElementSymbol)newSymbol).getShortName(); } else { needsAlias &= needsAlias(newAlias, (ElementSymbol)newSymbol); } } symbols.put(symbol, newAlias); if (visitor.namingContext.aliasColumns && needsAlias) { newSymbol = new AliasSymbol(Symbol.getShortName(symbol), newSymbol); ((AliasSymbol)newSymbol).setShortName(newAlias); } selectSymbols.set(i, newSymbol); } visitor.namingContext.currentSymbols = symbols; } @Override public void visit(StoredProcedure obj) { if (!obj.isPushedInQuery()) { return; } List<ElementSymbol> selectSymbols = obj.getProjectedSymbols(); LinkedHashMap<Expression, String> symbols = new LinkedHashMap<Expression, String>(selectSymbols.size()); for (int i = 0; i < selectSymbols.size(); i++) { ElementSymbol symbol = selectSymbols.get(i); symbols.put(symbol, symbol.getShortName()); } for (SPParameter param : obj.getParameters()) { visitNode(param.getExpression()); } visitor.namingContext.currentSymbols = symbols; } private boolean needsAlias(String newAlias, ElementSymbol symbol) { return !(symbol.getMetadataID() instanceof TempMetadataID) || !newAlias.equalsIgnoreCase(symbol.getShortName()); } /** * visit the query in definition order */ public void visit(Query obj) { visitNodes(obj.getWith()); if (obj.getOrderBy() != null || obj.getLimit() != null) { visitor.namingContext.aliasColumns = !stripColumnAliases; } visitNode(obj.getFrom()); if (this.aliasMapping != null) { HashSet<String> newSymbols = new HashSet<String>(); for (Map.Entry<String, String> entry : this.visitor.namingContext.groupNames.entrySet()) { if (!newSymbols.add(entry.getValue())) { throw new TeiidRuntimeException(new QueryPlannerException(QueryPlugin.Event.TEIID31126, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31126, entry.getValue()))); } } } visitNode(obj.getCriteria()); visitNode(obj.getGroupBy()); visitNode(obj.getHaving()); visitNode(obj.getSelect()); visitNode(obj.getOrderBy()); } public void visit(SubqueryFromClause obj) { visitor.createChildNamingContext(true); //first determine the original names List<Expression> exprs = obj.getCommand().getProjectedSymbols(); List<String> names = new ArrayList<String>(exprs.size()); for (int i = 0; i < exprs.size(); i++) { names.add(Symbol.getShortName(exprs.get(i))); } obj.getCommand().acceptVisitor(this); Map<String, String> viewGroup = new HashMap<String, String>(); int i = 0; //now map to the new names for (Entry<Expression, String> entry : visitor.namingContext.currentSymbols.entrySet()) { viewGroup.put(names.get(i++), entry.getValue()); } visitor.namingContext.parent.elementMap.put(obj.getName(), viewGroup); visitor.removeChildNamingContext(); obj.getGroupSymbol().setName(recontextGroup(obj.getGroupSymbol(), true)); } @Override public void visit(UnaryFromClause obj) { GroupSymbol symbol = obj.getGroup(); if (visitor.aliasGroups) { recontextGroup(symbol, false); } else { visitor.namingContext.groupNames.put(symbol.getName(), symbol.getNonCorrelationName()); } super.visit(obj); } /** * @param symbol */ private String recontextGroup(GroupSymbol symbol, boolean virtual) { String newAlias = null; while (true) { if (virtual) { newAlias = view_prefix + viewIndex++; } else { newAlias = table_prefix + groupIndex++; } if (correlationGroups == null || !correlationGroups.contains(newAlias)) { break; } } if (this.aliasMapping != null && symbol.getDefinition() != null) { String oldAlias = this.aliasMapping.get(symbol.getName()); if (oldAlias != null) { newAlias = oldAlias; if (newAlias.startsWith(table_prefix) || newAlias.startsWith(view_prefix)) { try { Integer.parseInt(newAlias.substring(2, newAlias.length())); throw new TeiidRuntimeException(new QueryPlannerException(QueryPlugin.Event.TEIID31127, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31127, newAlias))); } catch (NumberFormatException e) { } } } } visitor.namingContext.groupNames.put(symbol.getName(), newAlias); return newAlias; } public void visit(ScalarSubquery obj) { if (obj.shouldEvaluate()) { return; } visitor.createChildNamingContext(false); visitNode(obj.getCommand()); visitor.removeChildNamingContext(); } public void visit(SubqueryCompareCriteria obj) { visitNode(obj.getLeftExpression()); visitor.createChildNamingContext(false); visitNode(obj.getCommand()); visitor.removeChildNamingContext(); } public void visit(SubquerySetCriteria obj) { visitNode(obj.getExpression()); visitor.createChildNamingContext(false); visitNode(obj.getCommand()); visitor.removeChildNamingContext(); } public void visit(ExistsCriteria obj) { if (obj.shouldEvaluate()) { return; } visitor.createChildNamingContext(false); visitNode(obj.getCommand()); visitor.removeChildNamingContext(); } @Override public void visit(WithQueryCommand obj) { visitor.createChildNamingContext(false); visitNode(obj.getCommand()); visitor.removeChildNamingContext(); } public void visit(OrderBy obj) { //add/correct aliases if necessary for (int i = 0; i < obj.getVariableCount(); i++) { OrderByItem item = obj.getOrderByItems().get(i); Expression element = item.getSymbol(); visitNode(element); Expression expr = SymbolMap.getExpression(element); if (item.isUnrelated()) { item.setSymbol(expr); continue; } String name = null; if (visitor.namingContext.currentSymbols != null) { name = visitor.namingContext.currentSymbols.get(element); } if (name == null) { //this is a bit messy, because we have cloned to do the aliasing, there //is a chance that a subquery is throwing off the above get int pos = item.getExpressionPosition(); if (pos < visitor.namingContext.currentSymbols.size()) { ArrayList<Map.Entry<Expression, String>> list = new ArrayList<Map.Entry<Expression,String>>(visitor.namingContext.currentSymbols.entrySet()); name = list.get(pos).getValue(); expr = SymbolMap.getExpression(list.get(pos).getKey()); } else { name = Symbol.getShortName(element); } } boolean needsAlias = visitor.namingContext.aliasColumns; if (name == null) { continue; } if (expr instanceof ElementSymbol) { needsAlias &= needsAlias(name, (ElementSymbol)expr); } if (needsAlias) { element = new AliasSymbol(Symbol.getShortName(element), expr); } else { element = expr; if (expr instanceof ElementSymbol && visitor.namingContext.aliasColumns) { ((ElementSymbol)expr).setDisplayMode(DisplayMode.SHORT_OUTPUT_NAME); } } item.setSymbol(element); if (element instanceof Symbol) { ((Symbol)element).setShortName(name); } } } public void visit(Reference obj) { if (!obj.isCorrelated()) { return; } //we need to follow references to correct correlated variables org.teiid.query.optimizer.relational.AliasGenerator.NamingVisitor.SQLNamingContext sqlNamingContext = this.visitor.namingContext.parent; while (sqlNamingContext != null) { if (sqlNamingContext.groupNames.containsKey(obj.getExpression().getGroupSymbol().getName())) { visitNode(obj.getExpression()); return; } sqlNamingContext = sqlNamingContext.parent; } if (!this.visitor.namingContext.groupNames.containsKey(obj.getExpression().getGroupSymbol().getName())) { visitNode(obj.getExpression()); } else { // else - this is a naming conflict that is not handled gracefully } } public void setAliasMapping(Map<String, String> aliasMapping) { this.aliasMapping = aliasMapping; } public void setCorrelationGroups(Collection<String> correlationGroups) { this.correlationGroups = correlationGroups; } }