/* * 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.sql.visitor; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.teiid.core.types.ArrayImpl; import org.teiid.core.types.DataTypeManagerService; import org.teiid.core.util.PropertiesUtils; import org.teiid.core.util.StringUtil; import org.teiid.designer.annotation.Removed; import org.teiid.designer.query.sql.ISQLStringVisitor; import org.teiid.designer.query.sql.IToken; import org.teiid.designer.query.sql.lang.IComment; import org.teiid.designer.query.sql.symbol.IAggregateSymbol.Type; import org.teiid.designer.runtime.version.spi.ITeiidServerVersion; import org.teiid.designer.runtime.version.spi.TeiidServerVersion.Version; import org.teiid.language.SQLConstants; import org.teiid.language.SQLConstants.NonReserved; import org.teiid.language.SQLConstants.Reserved; import org.teiid.language.SQLConstants.Tokens; import org.teiid.metadata.AbstractMetadataRecord; import org.teiid.metadata.BaseColumn; import org.teiid.metadata.BaseColumn.NullType; import org.teiid.metadata.Column; import org.teiid.metadata.ForeignKey; import org.teiid.metadata.KeyRecord; import org.teiid.metadata.MetadataFactory; import org.teiid.metadata.Table; import org.teiid.query.metadata.DDLConstants; import org.teiid.query.parser.LanguageVisitor; import org.teiid.query.parser.TeiidNodeFactory; import org.teiid.query.parser.TeiidNodeFactory.ASTNodes; import org.teiid.query.sql.lang.AlterProcedure; import org.teiid.query.sql.lang.AlterTrigger; import org.teiid.query.sql.lang.AlterView; import org.teiid.query.sql.lang.ArrayTable; import org.teiid.query.sql.lang.BetweenCriteria; import org.teiid.query.sql.lang.CacheHint; import org.teiid.query.sql.lang.Comment; import org.teiid.query.sql.lang.CompareCriteria; import org.teiid.query.sql.lang.CompoundCriteria; import org.teiid.query.sql.lang.Create; import org.teiid.query.sql.lang.Create.CommitAction; import org.teiid.query.sql.lang.Criteria; import org.teiid.query.sql.lang.CriteriaSelector; import org.teiid.query.sql.lang.Delete; import org.teiid.query.sql.lang.Drop; import org.teiid.query.sql.lang.DynamicCommand; import org.teiid.query.sql.lang.ExistsCriteria; import org.teiid.query.sql.lang.ExpressionCriteria; import org.teiid.query.sql.lang.From; import org.teiid.query.sql.lang.FromClause; import org.teiid.query.sql.lang.GroupBy; import org.teiid.query.sql.lang.HasCriteria; import org.teiid.query.sql.lang.Insert; import org.teiid.query.sql.lang.Into; import org.teiid.query.sql.lang.IsDistinctCriteria; import org.teiid.query.sql.lang.IsNullCriteria; import org.teiid.query.sql.lang.JoinPredicate; import org.teiid.query.sql.lang.JoinType; import org.teiid.query.sql.lang.Labeled; import org.teiid.query.sql.lang.LanguageObject; import org.teiid.query.sql.lang.Limit; import org.teiid.query.sql.lang.MatchCriteria; import org.teiid.query.sql.lang.NamespaceItem; import org.teiid.query.sql.lang.NotCriteria; import org.teiid.query.sql.lang.ObjectColumn; import org.teiid.query.sql.lang.ObjectTable; import org.teiid.query.sql.lang.Option; import org.teiid.query.sql.lang.Option.MakeDep; import org.teiid.query.sql.lang.OrderBy; import org.teiid.query.sql.lang.OrderByItem; import org.teiid.query.sql.lang.PredicateCriteria; import org.teiid.query.sql.lang.ProjectedColumn; import org.teiid.query.sql.lang.Query; import org.teiid.query.sql.lang.QueryCommand; import org.teiid.query.sql.lang.SPParameter; import org.teiid.query.sql.lang.Select; import org.teiid.query.sql.lang.SetClause; import org.teiid.query.sql.lang.SetClauseList; import org.teiid.query.sql.lang.SetCriteria; import org.teiid.query.sql.lang.SetQuery; import org.teiid.query.sql.lang.SourceHint; import org.teiid.query.sql.lang.SourceHint.SpecificHint; import org.teiid.query.sql.lang.StoredProcedure; import org.teiid.query.sql.lang.SubqueryCompareCriteria; import org.teiid.query.sql.lang.SubqueryFromClause; import org.teiid.query.sql.lang.SubqueryHint; import org.teiid.query.sql.lang.SubquerySetCriteria; import org.teiid.query.sql.lang.TextColumn; import org.teiid.query.sql.lang.TextTable; import org.teiid.query.sql.lang.TranslateCriteria; import org.teiid.query.sql.lang.UnaryFromClause; import org.teiid.query.sql.lang.Update; import org.teiid.query.sql.lang.WithQueryCommand; import org.teiid.query.sql.lang.XMLColumn; import org.teiid.query.sql.lang.XMLTable; import org.teiid.query.sql.proc.AssignmentStatement; import org.teiid.query.sql.proc.Block; import org.teiid.query.sql.proc.BranchingStatement; import org.teiid.query.sql.proc.CommandStatement; import org.teiid.query.sql.proc.CreateProcedureCommand; import org.teiid.query.sql.proc.CreateUpdateProcedureCommand; import org.teiid.query.sql.proc.DeclareStatement; import org.teiid.query.sql.proc.ExceptionExpression; import org.teiid.query.sql.proc.IfStatement; import org.teiid.query.sql.proc.LoopStatement; import org.teiid.query.sql.proc.RaiseErrorStatement; import org.teiid.query.sql.proc.RaiseStatement; import org.teiid.query.sql.proc.ReturnStatement; import org.teiid.query.sql.proc.Statement; import org.teiid.query.sql.proc.TriggerAction; import org.teiid.query.sql.proc.WhileStatement; import org.teiid.query.sql.symbol.AggregateSymbol; import org.teiid.query.sql.symbol.AliasSymbol; import org.teiid.query.sql.symbol.Array; import org.teiid.query.sql.symbol.CaseExpression; import org.teiid.query.sql.symbol.Constant; import org.teiid.query.sql.symbol.DerivedColumn; 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.Function; import org.teiid.query.sql.symbol.GroupSymbol; import org.teiid.query.sql.symbol.JSONObject; import org.teiid.query.sql.symbol.MultipleElementSymbol; import org.teiid.query.sql.symbol.QueryString; import org.teiid.query.sql.symbol.Reference; import org.teiid.query.sql.symbol.ScalarSubquery; import org.teiid.query.sql.symbol.SearchedCaseExpression; import org.teiid.query.sql.symbol.Symbol; import org.teiid.query.sql.symbol.TextLine; import org.teiid.query.sql.symbol.WindowFunction; import org.teiid.query.sql.symbol.WindowSpecification; import org.teiid.query.sql.symbol.XMLAttributes; import org.teiid.query.sql.symbol.XMLCast; import org.teiid.query.sql.symbol.XMLElement; import org.teiid.query.sql.symbol.XMLExists; import org.teiid.query.sql.symbol.XMLForest; import org.teiid.query.sql.symbol.XMLNamespaces; import org.teiid.query.sql.symbol.XMLParse; import org.teiid.query.sql.symbol.XMLQuery; import org.teiid.query.sql.symbol.XMLSerialize; import org.teiid.translator.SourceSystemFunctions; /** * <p> * The SQLStringVisitor will visit a set of ast nodes and return the corresponding SQL string representation. * </p> */ public class SQLStringVisitor extends LanguageVisitor implements SQLConstants.Reserved, SQLConstants.NonReserved, SQLConstants.Tokens, DDLConstants, ISQLStringVisitor<LanguageObject> { private static final Set<String> INFIX_FUNCTIONS = new HashSet<String>(Arrays.asList(SQLConstants.Tokens.PLUS, SQLConstants.Tokens.MINUS, SQLConstants.Tokens.ALL_COLS, SQLConstants.Tokens.SLASH, SQLConstants.Tokens.CONCAT, SQLConstants.Tokens.DOUBLE_AMP)); /* * Converted from static field to function to allow version to be checked */ private static Map<String, String> builtinPrefixes(ITeiidServerVersion version) { Map<String, String> builtinPrefixes = new HashMap<String, String>(); for (Map.Entry<String, String> entry : MetadataFactory.builtinNamespaces(version).entrySet()) { builtinPrefixes.put(entry.getValue(), entry.getKey()); } return builtinPrefixes; } private static final HashSet<String> LENGTH_DATATYPES = new HashSet<String>( Arrays.asList( DataTypeManagerService.DefaultDataTypes.CHAR.getId(), DataTypeManagerService.DefaultDataTypes.CLOB.getId(), DataTypeManagerService.DefaultDataTypes.BLOB.getId(), DataTypeManagerService.DefaultDataTypes.OBJECT.getId(), DataTypeManagerService.DefaultDataTypes.XML.getId(), DataTypeManagerService.DefaultDataTypes.STRING.getId(), DataTypeManagerService.DefaultDataTypes.VARBINARY.getId(), DataTypeManagerService.DefaultDataTypes.BIG_INTEGER.getId())); private static final HashSet<String> PRECISION_DATATYPES = new HashSet<String>( Arrays.asList(DataTypeManagerService.DefaultDataTypes.BIG_DECIMAL.getId())); /** * Undefined */ public static final String UNDEFINED = "<undefined>"; //$NON-NLS-1$ private static final String BEGIN_HINT = "/*+"; //$NON-NLS-1$ private static final String END_HINT = "*/"; //$NON-NLS-1$ private static final char ID_ESCAPE_CHAR = '\"'; /** * Indicator for disabling comments across multiple instances of this class. * Possibilities of calling new instances of this class from this class when * calling append(LanguageObject). This will convert the LanguageObject to * a String using a new instance of this class. Since we only want comments * added by the single 'top-most' instance then this set must be empty for * comments to be added. */ private static Set<String> ALLOW_COMMENTS = new HashSet<String>(); protected StringBuilder parts = new StringBuilder(); private boolean shortNameOnly = false; /** * @param teiidVersion */ public SQLStringVisitor(ITeiidServerVersion teiidVersion) { super(teiidVersion); } /** * Helper to quickly get the parser string for an object using the visitor. * * @param obj Language object * * @return String SQL String for obj */ public static final String getSQLString(LanguageObject obj) { if (obj == null) { return UNDEFINED; } SQLStringVisitor visitor = new SQLStringVisitor(obj.getTeiidVersion()); return visitor.returnSQLString(obj); } /** * @param languageObject * @return sql representation of {@link LanguageObject} */ @Override public String returnSQLString(LanguageObject languageObject) { if (languageObject == null) { return UNDEFINED; } isApplicable(languageObject); languageObject.acceptVisitor(this); return getSQLString(); } /** * Retrieve completed string from the visitor. * * @return Complete SQL string for the visited nodes */ public String getSQLString() { return this.parts.toString().trim(); } /** * @return the shortNameOnly */ public boolean isShortNameOnly() { return this.shortNameOnly; } /** * @param shortNameOnly the shortNameOnly to set */ private void setShortNameOnly(boolean shortNameOnly) { this.shortNameOnly = shortNameOnly; } protected void visitNode(LanguageObject obj) { if (obj == null) { append(UNDEFINED); return; } isApplicable(obj); obj.acceptVisitor(this); } protected void append(Object value) { if (value instanceof LanguageObject) { disableComments((LanguageObject) value); } this.parts.append(value); if (value instanceof LanguageObject) { enableComments((LanguageObject) value); } } protected void beginClause(int level) { append(SPACE); } private Constant newConstant(Object value) { Constant constant = createASTNode(ASTNodes.CONSTANT); constant.setValue(value); return constant; } private int nextAvailableSpace(String sql, int index) { for (int i = index - 1; i < sql.length(); ++i) { char c = sql.charAt(i); if (Character.isWhitespace(c)) return i + 1; } return sql.length(); } /** * based on {@link #outputDisplayName(String)} * * @param name * @return escaped version of name, eg. x.from.y becomes x."from".y */ public String displayName(IToken token) { if (! token.isId()) return token.getText(); StringBuffer buf = new StringBuffer(); String[] pathParts = token.getText().split("\\."); //$NON-NLS-1$ for (int i = 0; i < pathParts.length; i++) { if (i > 0) { buf.append(Symbol.SEPARATOR); } buf.append(escapeSinglePart(pathParts[i])); } return buf.toString(); } private String escape(String text, String character, boolean optional) { String target = StringUtil.Constants.ESCAPE + character; String replacement = StringUtil.Constants.REGEX_ESCAPE + character + (optional ? StringUtil.Constants.QMARK : StringUtil.Constants.EMPTY_STRING); text = text.replaceAll(target, replacement); return text; } /** * @param sql * @param comment * @return */ private int calculateLocation(String sql, Comment comment) { int cmtIdx = comment.getOffset(); LinkedList<IToken> preTokens = new LinkedList(comment.getPreTokens()); if (preTokens.isEmpty()) return cmtIdx; // can happen if offset is 0 and comment is at the start // i : case-insensitive mode // s: include line terminators in . matchings StringBuffer regex = new StringBuffer("(?is)"); //$NON-NLS-1$ Iterator<IToken> iterator = preTokens.iterator(); while(iterator.hasNext()) { IToken token = iterator.next(); String text = displayName(token); // Must escape question marks first since optional is represented by question marks in regex text = escape(text, SQLConstants.Tokens.QMARK, false); text = escape(text, StringUtil.Constants.SPEECH_MARK, true); text = escape(text, StringUtil.Constants.QUOTE, true); text = escape(text, SQLConstants.Tokens.LPAREN, true); text = escape(text, SQLConstants.Tokens.RPAREN, true); // The zero at the end of a time is optional, eg. 19:00:02.50, so // gets dropped by the production. Thus, need to make it // optional. Have to do it prior to escaping DOT, otherwise the // replace regex becomes consumed by backslashes! String target = "([0-9][0-9]:[0-9][0-9]:[0-9][0-9]\\.[0-9])0"; //$NON-NLS-1$ String replacement = "$1[0-9]?'"; //$NON-NLS-1$ text = text.replaceAll(target, replacement); // The source hint is parseable as /*+ sh ... */ but all spaces are removed // between the + and sh upon conversion. target = "\\/\\*\\+\\s*sh"; replacement = "/*+sh"; text = text.replaceAll(target, replacement); text = escape(text, SQLConstants.Tokens.DOT, false); text = escape(text, StringUtil.Constants.LBRACE, false); // Makes right brace optional since fn{ is dropped as the prefix of functions // and its closing brace will remain as a pre token text = escape(text, StringUtil.Constants.RBRACE, true); text = escape(text, StringUtil.Constants.FORWARD_SLASH, false); text = escape(text, StringUtil.Constants.STAR, false); text = escape(text, StringUtil.Constants.PLUS, false); text = escape(text, StringUtil.Constants.PIPE, false); regex.append(text); if (iterator.hasNext()) regex.append(DOT).append(StringUtil.Constants.STAR).append(QMARK); } Pattern pattern = Pattern.compile(regex.toString()); Matcher matcher = pattern.matcher(sql); // // Find ALL the possible sub sequences that the matcher could match. // Most of the time it should be only one but sometimes a SELECT subquery // could have the same WHERE condition as the outer master query. // WITH x AS (SELECT a FROM db.g WHERE b = aString /* Comment 1 */) // SELECT a FROM db.g WHERE b = aString /* Comment 2 */ List<Integer> results = new ArrayList<Integer>(); boolean result = true; while (result) { result = matcher.find(); if (result) { // We know that the matcher has succeeded once but is possible that // since its a reluctant matcher it is in fact finding a result far earlier // than required. results.add(matcher.end()); } } if (results.isEmpty()) return -1; // // Loop through the candidates and choose the pair {start, end} which has an end // value closest to the offset of the comment // Integer pref = results.get(0); int diff = Math.abs(pref - cmtIdx); for (Integer poss : results) { int pdiff = Math.abs(poss - cmtIdx); if (pdiff < diff) pref = poss; } int offset = pref + 1; if (offset != cmtIdx) cmtIdx = offset; // // At this point, we have a location index for the comment. // However, its still possible, despite best efforts that that index // is within a term. Thus, check the index finally... // return nextAvailableSpace(sql, cmtIdx); } @Override public void enableComments(Object obj) { ALLOW_COMMENTS.remove(obj.getClass().getSimpleName() + System.identityHashCode(obj)); } @Override public void disableComments(Object obj) { ALLOW_COMMENTS.add(obj.getClass().getSimpleName() + System.identityHashCode(obj)); } private Set<IComment> seenComments = new HashSet<IComment>(); protected void addComments(LanguageObject obj) { // Comments disabled if this set contains identity hashcodes if (!ALLOW_COMMENTS.isEmpty()) return; Set<Comment> comments = obj.getComments(); for (Comment comment : comments) { if (seenComments.contains(comment)) continue; String sql = parts.toString(); int insertIdx = calculateLocation(sql, comment); if (insertIdx == -1) continue; // Handling trailing comments if (insertIdx >= sql.length()) { // insert index is the end of the sql string if (! sql.endsWith(SPACE)) parts.append(SPACE); // avoid appending a space since the sql doesn't end with one // so will most likely be added prior to the next token parts.append(comment.getText()); if (! comment.isMultiLine()) parts.append(NEWLINE); } else { // Most of the comments with be dealt with here String text = comment.getText(); if (comment.isMultiLine()) text = text + SPACE; else text = text + NEWLINE; parts.insert(insertIdx, text); } seenComments.add(comment); } } // ############ Visitor methods for language objects #################### @Override public void visit(BetweenCriteria obj) { // Turn off adding comments until the query is completely formed disableComments(obj); visitNode(obj.getExpression()); append(SPACE); if (obj.isNegated()) { append(NOT); append(SPACE); } append(BETWEEN); append(SPACE); visitNode(obj.getLowerExpression()); append(SPACE); append(AND); append(SPACE); visitNode(obj.getUpperExpression()); enableComments(obj); addComments(obj); } @Override public void visit(CaseExpression obj) { // Turn off adding comments until the query is completely formed disableComments(obj); append(CASE); append(SPACE); visitNode(obj.getExpression()); append(SPACE); for (int i = 0; i < obj.getWhenCount(); i++) { append(WHEN); append(SPACE); visitNode(obj.getWhenExpression(i)); append(SPACE); append(THEN); append(SPACE); visitNode(obj.getThenExpression(i)); append(SPACE); } if (obj.getElseExpression() != null) { append(ELSE); append(SPACE); visitNode(obj.getElseExpression()); append(SPACE); } append(END); enableComments(obj); addComments(obj); } @Override public void visit(CompareCriteria obj) { // Turn off adding comments until the query is completely formed disableComments(obj); Expression leftExpression = obj.getLeftExpression(); visitNode(leftExpression); append(SPACE); append(obj.getOperatorAsString()); append(SPACE); Expression rightExpression = obj.getRightExpression(); visitNode(rightExpression); enableComments(obj); addComments(obj); } @Override public void visit(CompoundCriteria obj) { // Turn off adding comments until the query is completely formed disableComments(obj); // Get operator string int operator = obj.getOperator(); String operatorStr = ""; //$NON-NLS-1$ if (operator == CompoundCriteria.AND) { operatorStr = AND; } else if (operator == CompoundCriteria.OR) { operatorStr = OR; } // Get criteria List<Criteria> subCriteria = obj.getCriteria(); // Build parts if (subCriteria.size() == 1) { // Special case - should really never happen, but we are tolerant Criteria firstChild = subCriteria.get(0); visitNode(firstChild); } else { // Add first criteria Iterator<Criteria> iter = subCriteria.iterator(); while (iter.hasNext()) { // Add criteria Criteria crit = iter.next(); append(Tokens.LPAREN); visitNode(crit); append(Tokens.RPAREN); if (iter.hasNext()) { // Add connector append(SPACE); append(operatorStr); append(SPACE); } } } enableComments(obj); addComments(obj); } @Override public void visit(Delete obj) { // Turn off adding comments until the query is completely formed disableComments(obj); // add delete clause append(DELETE); addSourceHint(obj.getSourceHint()); append(SPACE); // add from clause append(FROM); append(SPACE); visitNode(obj.getGroup()); // add where clause if (obj.getCriteria() != null) { beginClause(0); visitCriteria(WHERE, obj.getCriteria()); } // Option clause if (obj.getOption() != null) { beginClause(0); visitNode(obj.getOption()); } enableComments(obj); addComments(obj); } @Override public void visit(From obj) { // Turn off adding comments until the query is completely formed disableComments(obj); append(FROM); beginClause(1); registerNodes(obj.getClauses(), 0); enableComments(obj); addComments(obj); } @Override public void visit(GroupBy obj) { // Turn off adding comments until the query is completely formed disableComments(obj); append(GROUP); append(SPACE); append(BY); append(SPACE); if (obj.isRollup()) { append(ROLLUP); append(Tokens.LPAREN); } registerNodes(obj.getSymbols(), 0); if (obj.isRollup()) { append(Tokens.RPAREN); } enableComments(obj); addComments(obj); } @Override public void visit(Insert obj) { // Turn off adding comments until the query is completely formed disableComments(obj); if (obj.isMerge()) { append(MERGE); } else { append(INSERT); } addSourceHint(obj.getSourceHint()); append(SPACE); append(INTO); append(SPACE); visitNode(obj.getGroup()); if (!obj.getVariables().isEmpty()) { beginClause(2); // Columns clause List<ElementSymbol> vars = obj.getVariables(); if (vars != null) { append("("); //$NON-NLS-1$ setShortNameOnly(true); registerNodes(vars, 0); setShortNameOnly(false); append(")"); //$NON-NLS-1$ } } beginClause(1); if (obj.getQueryExpression() != null) { visitNode(obj.getQueryExpression()); // } else if (obj.getTupleSource() != null) { // append(VALUES); // append(" (...)"); //$NON-NLS-1$ } else if (obj.getValues() != null) { append(VALUES); beginClause(2); append("("); //$NON-NLS-1$ registerNodes(obj.getValues(), 0); append(")"); //$NON-NLS-1$ } // Option clause if (obj.getOption() != null) { beginClause(1); visitNode(obj.getOption()); } enableComments(obj); addComments(obj); } private String buildTableOptions(Table table) { StringBuilder options = new StringBuilder(); addCommonOptions(options, table); if (table.isMaterialized()) { addOption(options, MATERIALIZED, table.isMaterialized()); if (table.getMaterializedTable() != null) { addOption(options, MATERIALIZED_TABLE, table.getMaterializedTable().getName()); } } if (table.supportsUpdate()) { addOption(options, UPDATABLE, table.supportsUpdate()); } if (table.getCardinality() != -1) { addOption(options, CARDINALITY, table.getCardinality()); } if (!table.getProperties().isEmpty()) { for (String key:table.getProperties().keySet()) { addOption(options, key, table.getProperty(key, false)); } } return options.toString(); } private void addCommonOptions(StringBuilder sb, AbstractMetadataRecord record) { if (record.getUUID() != null && !record.getUUID().startsWith("tid:")) { //$NON-NLS-1$ addOption(sb, UUID, record.getUUID()); } if (record.getAnnotation() != null) { addOption(sb, ANNOTATION, record.getAnnotation()); } if (record.getNameInSource() != null && !record.getNameInSource().equals(record.getName())) { addOption(sb, NAMEINSOURCE, record.getNameInSource()); } } private void buildContraints(Table table) { addConstraints(table.getAccessPatterns(), "AP", ACCESSPATTERN); //$NON-NLS-1$ KeyRecord pk = table.getPrimaryKey(); if (pk != null) { addConstraint("PK", PRIMARY_KEY, pk, true); //$NON-NLS-1$ } addConstraints(table.getUniqueKeys(), UNIQUE, UNIQUE); addConstraints(table.getIndexes(), INDEX, INDEX); addConstraints(table.getFunctionBasedIndexes(), INDEX, INDEX); for (int i = 0; i < table.getForeignKeys().size(); i++) { ForeignKey key = table.getForeignKeys().get(i); addConstraint("FK" + i, FOREIGN_KEY, key, false); //$NON-NLS-1$ append(SPACE); append(REFERENCES); if (key.getReferenceTableName() != null) { append(SPACE); GroupSymbol gs = createASTNode(ASTNodes.GROUP_SYMBOL); gs.setName(key.getReferenceTableName()); append(gs.getName()); } append(SPACE); addNames(key.getReferenceColumns()); appendOptions(key); } } private void addConstraints(List<KeyRecord> constraints, String defaultName, String type) { for (int i = 0; i < constraints.size(); i++) { KeyRecord constraint = constraints.get(i); addConstraint(defaultName + i, type, constraint, true); } } private void addConstraint(String defaultName, String type, KeyRecord constraint, boolean addOptions) { append(COMMA); append(NEWLINE); append(TAB); boolean nameMatches = defaultName.equals(constraint.getName()); if (!nameMatches) { append(CONSTRAINT); append(SPACE); append(escapeSinglePart(constraint.getName())); append(SPACE); } append(type); addColumns(constraint.getColumns(), false); if (addOptions) { appendOptions(constraint); } } private void addColumns(List<Column> columns, boolean includeType) { append(LPAREN); boolean first = true; for (Column c:columns) { if (first) { first = false; } else { append(COMMA); append(SPACE); } if (includeType) { appendColumn(c, true, includeType); appendColumnOptions(c); } else if (c.getParent() instanceof KeyRecord) { //function based column append(c.getNameInSource()); } else { append(escapeSinglePart(c.getName())); } } append(RPAREN); } private void addNames(List<String> columns) { if (columns != null) { append(LPAREN); boolean first = true; for (String c:columns) { if (first) { first = false; } else { append(COMMA); append(SPACE); } append(escapeSinglePart(c)); } append(RPAREN); } } private void visit(Column column) { append(NEWLINE); append(TAB); appendColumn(column, true, true); if (column.isAutoIncremented()) { append(SPACE); append(DDLConstants.AUTO_INCREMENT); } appendDefault(column); // options appendColumnOptions(column); } private void appendDefault(BaseColumn column) { if (column.getDefaultValue() != null) { append(SPACE); append(DEFAULT); append(SPACE); append(TICK); append(StringUtil.replaceAll(column.getDefaultValue(), TICK, TICK + TICK)); append(TICK); } } private void appendColumn(BaseColumn column, boolean includeName, boolean includeType) { if (includeName) { append(escapeSinglePart(column.getName())); } if (includeType) { String runtimeTypeName = column.getDatatype().getRuntimeTypeName(); if (includeName) { append(SPACE); } append(runtimeTypeName); if (LENGTH_DATATYPES.contains(runtimeTypeName)) { if (column.getLength() != 0 && column.getLength() != column.getDatatype().getLength()) { append(LPAREN); append(column.getLength()); append(RPAREN); } } else if (PRECISION_DATATYPES.contains(runtimeTypeName) && (column.getPrecision() != column.getDatatype().getPrecision() || column.getScale() != column.getDatatype().getScale())) { append(LPAREN); append(column.getPrecision()); if (column.getScale() != 0) { append(COMMA); append(column.getScale()); } append(RPAREN); } if (column.getNullType() == NullType.No_Nulls) { append(SPACE); append(NOT_NULL); } } } private void appendColumnOptions(BaseColumn column) { StringBuilder options = new StringBuilder(); addCommonOptions(options, column); if (!column.getDatatype().isBuiltin()) { addOption(options, UDT, column.getDatatype().getName() + "("+column.getLength()+ ", " +column.getPrecision()+", " + column.getScale()+ ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ } if (column.getDatatype().getRadix() != 0 && column.getRadix() != column.getDatatype().getRadix()) { addOption(options, RADIX, column.getRadix()); } if (column instanceof Column) { buildColumnOptions((Column)column, options); } if (options.length() != 0) { append(SPACE); append(OPTIONS); append(SPACE); append(LPAREN); append(options); append(RPAREN); } } private void buildColumnOptions(Column column, StringBuilder options) { if (!column.isSelectable()) { addOption(options, SELECTABLE, column.isSelectable()); } // if table is already updatable, then columns are implicitly updatable. if (!column.isUpdatable() && column.getParent() instanceof Table && ((Table)column.getParent()).supportsUpdate()) { addOption(options, UPDATABLE, column.isUpdatable()); } if (column.isCurrency()) { addOption(options, CURRENCY, column.isCurrency()); } // only record if not default if (!column.isCaseSensitive() && column.getDatatype().isCaseSensitive()) { addOption(options, CASE_SENSITIVE, column.isCaseSensitive()); } if (!column.isSigned() && column.getDatatype().isSigned()) { addOption(options, SIGNED, column.isSigned()); } if (column.isFixedLength()) { addOption(options, FIXED_LENGTH, column.isFixedLength()); } // length and octet length should be same. so this should be never be true. //TODO - this is not quite valid since we are dealing with length representing chars in UTF-16, then there should be twice the bytes if (column.getCharOctetLength() != 0 && column.getLength() != column.getCharOctetLength()) { addOption(options, CHAR_OCTET_LENGTH, column.getCharOctetLength()); } // by default the search type is default data type search, so avoid it. if (column.getSearchType() != null && (!column.getSearchType().equals(column.getDatatype().getSearchType()) || column.isSearchTypeSet())) { addOption(options, SEARCHABLE, column.getSearchType().name()); } if (column.getMinimumValue() != null) { addOption(options, MIN_VALUE, column.getMinimumValue()); } if (column.getMaximumValue() != null) { addOption(options, MAX_VALUE, column.getMaximumValue()); } if (column.getNativeType() != null) { addOption(options, NATIVE_TYPE, column.getNativeType()); } if (column.getNullValues() != -1) { addOption(options, NULL_VALUE_COUNT, column.getNullValues()); } if (column.getDistinctValues() != -1) { addOption(options, DISTINCT_VALUES, column.getDistinctValues()); } buildOptions(column, options); } private void appendOptions(AbstractMetadataRecord record) { StringBuilder options = new StringBuilder(); buildOptions(record, options); if (options.length() != 0) { append(SPACE); append(OPTIONS); append(SPACE); append(LPAREN); append(options); append(RPAREN); } } private void buildOptions(AbstractMetadataRecord record, StringBuilder options) { if (!record.getProperties().isEmpty()) { for (Map.Entry<String, String> entry:record.getProperties().entrySet()) { addOption(options, entry.getKey(), entry.getValue()); } } } private void addOption(StringBuilder sb, String key, Object value) { if (sb.length() != 0) { sb.append(COMMA).append(SPACE); } Constant c = createASTNode(ASTNodes.CONSTANT); c.setValue(value); value = c; if (key != null && key.length() > 2 && key.charAt(0) == '{') { String origKey = key; int index = key.indexOf('}'); if (index > 1) { String uri = key.substring(1, index); key = key.substring(index + 1, key.length()); String prefix = builtinPrefixes(getTeiidVersion()).get(uri); if (prefix != null) { key = prefix + ":" + key; //$NON-NLS-1$ } else { key = origKey; } } } sb.append(escapeSinglePart(key)); append(SPACE); append(value); } private String addTableBody(Table table) { String name = escapeSinglePart(table.getName()); append(name); if (table.getColumns() != null) { append(SPACE); append(LPAREN); boolean first = true; for (Column c:table.getColumns()) { if (first) { first = false; } else { append(COMMA); } visit(c); } buildContraints(table); append(NEWLINE); append(RPAREN); } // options String options = buildTableOptions(table); if (!options.isEmpty()) { append(SPACE); append(OPTIONS); append(SPACE); append(LPAREN); append(options); append(RPAREN); } return name; } private void visit9( Create obj ) { append(CREATE); append(SPACE); if (obj.getTableMetadata() != null) { append(FOREIGN); append(SPACE); append(TEMPORARY); append(SPACE); append(TABLE); append(SPACE); addTableBody(obj.getTableMetadata()); append(SPACE); append(ON); append(SPACE); outputLiteral(String.class, false, obj.getOn()); return; } append(LOCAL); append(SPACE); append(TEMPORARY); append(SPACE); append(TABLE); append(SPACE); visitNode(obj.getTable()); append(SPACE); // Columns clause List<Column> columns = obj.getColumns(); append("("); //$NON-NLS-1$ Iterator<Column> iter = columns.iterator(); while (iter.hasNext()) { Column element = iter.next(); outputDisplayName(element.getName()); append(SPACE); if (element.isAutoIncremented()) { append(NonReserved.SERIAL); } else { append(element.getRuntimeType()); if (element.getNullType() == NullType.No_Nulls) { append(SPACE); append(NOT); append(SPACE); append(NULL); } } if (iter.hasNext()) { append(", "); //$NON-NLS-1$ } } if (!obj.getPrimaryKey().isEmpty()) { append(", "); //$NON-NLS-1$ append(PRIMARY); append(" "); //$NON-NLS-1$ append(NonReserved.KEY); append(Tokens.LPAREN); Iterator<ElementSymbol> pkiter = obj.getPrimaryKey().iterator(); while (pkiter.hasNext()) { outputShortName(pkiter.next()); if (pkiter.hasNext()) { append(", "); //$NON-NLS-1$ } } append(Tokens.RPAREN); } append(Tokens.RPAREN); CommitAction commitAction = obj.getCommitAction(); if (commitAction != null) { append(Tokens.SPACE); append(Reserved.ON); append(Tokens.SPACE); append(Reserved.COMMIT); append(Tokens.SPACE); switch (commitAction) { case PRESERVE_ROWS: append(NonReserved.PRESERVE); append(Tokens.SPACE); append(Reserved.ROWS); break; } } } @Override public void visit(Create obj) { // Turn off adding comments until the query is completely formed disableComments(obj); visit9(obj); enableComments(obj); addComments(obj); } @Override public void visit(Drop obj) { // Turn off adding comments until the query is completely formed disableComments(obj); append(DROP); append(SPACE); append(TABLE); append(SPACE); visitNode(obj.getTable()); enableComments(obj); addComments(obj); } @Override public void visit(IsNullCriteria obj) { // Turn off adding comments until the query is completely formed disableComments(obj); Expression expr = obj.getExpression(); appendNested(expr); append(SPACE); append(IS); append(SPACE); if (obj.isNegated()) { append(NOT); append(SPACE); } append(NULL); enableComments(obj); addComments(obj); } @Override public void visit(JoinPredicate obj) { // Turn off adding comments until the query is completely formed disableComments(obj); addHintComment(obj); if (obj.hasHint()) { append("(");//$NON-NLS-1$ } // left clause FromClause leftClause = obj.getLeftClause(); if (leftClause instanceof JoinPredicate && !((JoinPredicate)leftClause).hasHint()) { append("("); //$NON-NLS-1$ visitNode(leftClause); append(")"); //$NON-NLS-1$ } else { visitNode(leftClause); } // join type append(SPACE); visitNode(obj.getJoinType()); append(SPACE); // right clause FromClause rightClause = obj.getRightClause(); if (rightClause instanceof JoinPredicate && !((JoinPredicate)rightClause).hasHint()) { append("("); //$NON-NLS-1$ visitNode(rightClause); append(")"); //$NON-NLS-1$ } else { visitNode(rightClause); } // join criteria List joinCriteria = obj.getJoinCriteria(); if (joinCriteria != null && joinCriteria.size() > 0) { append(SPACE); append(ON); append(SPACE); Iterator critIter = joinCriteria.iterator(); while (critIter.hasNext()) { Criteria crit = (Criteria)critIter.next(); if (crit instanceof PredicateCriteria || crit instanceof NotCriteria) { visitNode(crit); } else { append("("); //$NON-NLS-1$ visitNode(crit); append(")"); //$NON-NLS-1$ } if (critIter.hasNext()) { append(SPACE); append(AND); append(SPACE); } } } if (obj.hasHint()) { append(")"); //$NON-NLS-1$ } enableComments(obj); addComments(obj); } private void addHintComment(FromClause obj) { if (! obj.hasHint()) return; append(BEGIN_HINT); append(SPACE); if (obj.isOptional()) { append(Option.OPTIONAL); append(SPACE); } if (obj.getMakeDep() != null) { append(Option.MAKEDEP); appendMakeDepOptions(obj.getMakeDep()); append(SPACE); } if (obj.isMakeNotDep()) { append(Option.MAKENOTDEP); append(SPACE); } // Will return false if NOT less than Teiid 8.12.4 // if (obj.isMakeInd()) { // append(FromClause.MAKEIND); // append(SPACE); // } // Will return null if less than Teiid 8.12.4 if (obj.getMakeInd() != null) { append(MAKEIND); appendMakeDepOptions(obj.getMakeInd()); append(SPACE); } if (obj.isNoUnnest()) { append(SubqueryHint.NOUNNEST); append(SPACE); } if (obj.isPreserve()) { append(FromClause.PRESERVE); append(SPACE); } append(END_HINT); append(SPACE); } @Override public void visit(JoinType obj) { // Turn off adding comments until the query is completely formed disableComments(obj); String[] output = null; switch (obj.getKind()) { case JOIN_ANTI_SEMI: output = new String[] {"ANTI SEMI", SPACE, JOIN}; //$NON-NLS-1$ break; case JOIN_CROSS: output = new String[] {CROSS, SPACE, JOIN}; break; case JOIN_FULL_OUTER: output = new String[] {FULL, SPACE, OUTER, SPACE, JOIN}; break; case JOIN_INNER: output = new String[] {INNER, SPACE, JOIN}; break; case JOIN_LEFT_OUTER: output = new String[] {LEFT, SPACE, OUTER, SPACE, JOIN}; break; case JOIN_RIGHT_OUTER: output = new String[] {RIGHT, SPACE, OUTER, SPACE, JOIN}; break; case JOIN_SEMI: output = new String[] {"SEMI", SPACE, JOIN}; //$NON-NLS-1$ break; case JOIN_UNION: output = new String[] {UNION, SPACE, JOIN}; break; default: throw new AssertionError(); } for (String part : output) { append(part); } enableComments(obj); addComments(obj); } @Override public void visit(MatchCriteria obj) { // Turn off adding comments until the query is completely formed disableComments(obj); visitNode(obj.getLeftExpression()); append(SPACE); if (obj.isNegated()) { append(NOT); append(SPACE); } switch (obj.getMode()) { case SIMILAR: append(SIMILAR); append(SPACE); append(TO); break; case LIKE: append(LIKE); break; case REGEX: append(LIKE_REGEX); break; } append(SPACE); visitNode(obj.getRightExpression()); if (obj.getEscapeChar() != MatchCriteria.NULL_ESCAPE_CHAR) { append(SPACE); append(ESCAPE); append(SPACE); outputLiteral(String.class, false, obj.getEscapeChar()); } enableComments(obj); addComments(obj); } @Override public void visit(NotCriteria obj) { // Turn off adding comments until the query is completely formed disableComments(obj); append(NOT); append(" ("); //$NON-NLS-1$ visitNode(obj.getCriteria()); append(")"); //$NON-NLS-1$ enableComments(obj); addComments(obj); } @Override public void visit(Option obj) { // Turn off adding comments until the query is completely formed disableComments(obj); append(OPTION); Collection<String> groups = obj.getDependentGroups(); if (groups != null && groups.size() > 0) { append(" "); //$NON-NLS-1$ append(MAKEDEP); append(" "); //$NON-NLS-1$ Iterator<String> iter = groups.iterator(); Iterator<MakeDep> iter1 = obj.getMakeDepOptions().iterator(); while (iter.hasNext()) { outputDisplayName(iter.next()); appendMakeDepOptions(iter1.next()); if (iter.hasNext()) { append(", ");//$NON-NLS-1$ } } } groups = obj.getNotDependentGroups(); if (groups != null && groups.size() > 0) { append(" "); //$NON-NLS-1$ append(MAKENOTDEP); append(" "); //$NON-NLS-1$ Iterator<String> iter = groups.iterator(); while (iter.hasNext()) { outputDisplayName(iter.next()); if (iter.hasNext()) { append(", ");//$NON-NLS-1$ } } } groups = obj.getNoCacheGroups(); if (groups != null && groups.size() > 0) { append(" "); //$NON-NLS-1$ append(NOCACHE); append(" "); //$NON-NLS-1$ Iterator<String> iter = groups.iterator(); while (iter.hasNext()) { outputDisplayName(iter.next()); if (iter.hasNext()) { append(", ");//$NON-NLS-1$ } } } else if (obj.isNoCache()) { append(" "); //$NON-NLS-1$ append(NOCACHE); } enableComments(obj); addComments(obj); } /** * @param makedep * @return this visitor */ public SQLStringVisitor appendMakeDepOptions(MakeDep makedep) { boolean parens = false; if (makedep.getMax() != null || makedep.isJoin()) { append(Tokens.LPAREN); parens = true; } boolean space = false; if (makedep.getMax() != null) { if (space) { append(SPACE); } else { space = true; } append(NonReserved.MAX); append(Tokens.COLON); append(makedep.getMax()); } if (makedep.getJoin() != null) { if (space) { append(SPACE); } else { space = true; } if (!makedep.getJoin()) { append(NO); append(SPACE); } append(JOIN); } if (parens) { append(Tokens.RPAREN); } return this; } @Override public void visit(OrderBy obj) { // Turn off adding comments until the query is completely formed disableComments(obj); append(ORDER); append(SPACE); append(BY); append(SPACE); registerNodes(obj.getOrderByItems(), 0); enableComments(obj); addComments(obj); } @Override public void visit(OrderByItem obj) { // Turn off adding comments until the query is completely formed disableComments(obj); Expression ses = obj.getSymbol(); if (ses instanceof AliasSymbol) { AliasSymbol as = (AliasSymbol)ses; outputDisplayName(as.getOutputName()); } else { visitNode(ses); } if (!obj.isAscending()) { append(SPACE); append(DESC); } // Don't print default "ASC" if (obj.getNullOrdering() != null) { append(SPACE); append(NonReserved.NULLS); append(SPACE); append(obj.getNullOrdering().name()); } enableComments(obj); addComments(obj); } @Override public void visit(DynamicCommand obj) { // Turn off adding comments until the query is completely formed disableComments(obj); append(EXECUTE); append(SPACE); append(IMMEDIATE); append(SPACE); visitNode(obj.getSql()); if (obj.isAsClauseSet()) { beginClause(1); append(AS); append(SPACE); for (int i = 0; i < obj.getAsColumns().size(); i++) { ElementSymbol symbol = (ElementSymbol)obj.getAsColumns().get(i); outputShortName(symbol); append(SPACE); append(getDataTypeManager().getDataTypeName(symbol.getType())); if (i < obj.getAsColumns().size() - 1) { append(", "); //$NON-NLS-1$ } } } if (obj.getIntoGroup() != null) { beginClause(1); append(INTO); append(SPACE); visitNode(obj.getIntoGroup()); } if (obj.getUsing() != null && !obj.getUsing().isEmpty()) { beginClause(1); append(USING); append(SPACE); visitNode(obj.getUsing()); } if (obj.getUpdatingModelCount() > 0) { beginClause(1); append(UPDATE); append(SPACE); if (obj.getUpdatingModelCount() > 1) { append("*"); //$NON-NLS-1$ } else { append("1"); //$NON-NLS-1$ } } enableComments(obj); addComments(obj); } @Override public void visit(SetClauseList obj) { // Turn off adding comments until the query is completely formed disableComments(obj); for (Iterator<SetClause> iterator = obj.getClauses().iterator(); iterator.hasNext();) { SetClause clause = iterator.next(); visitNode(clause); if (iterator.hasNext()) { append(", "); //$NON-NLS-1$ } } enableComments(obj); addComments(obj); } @Override public void visit(SetClause obj) { // Turn off adding comments until the query is completely formed disableComments(obj); ElementSymbol symbol = obj.getSymbol(); outputShortName(symbol); append(" = "); //$NON-NLS-1$ visitNode(obj.getValue()); enableComments(obj); addComments(obj); } @Override public void visit(WithQueryCommand obj) { // Turn off adding comments until the query is completely formed disableComments(obj); visitNode(obj.getGroupSymbol()); append(SPACE); if (obj.getColumns() != null && !obj.getColumns().isEmpty()) { append(Tokens.LPAREN); setShortNameOnly(true); registerNodes(obj.getColumns(), 0); setShortNameOnly(false); append(Tokens.RPAREN); append(SPACE); } append(AS); if (obj.isNoInline()) { append(SPACE); append(BEGIN_HINT); append(SPACE); append(WithQueryCommand.NO_INLINE); append(SPACE); append(END_HINT); } append(SPACE); append(Tokens.LPAREN); if (obj.getCommand() == null) { append("<dependent values>"); //$NON-NLS-1$ } else { visitNode(obj.getCommand()); } append(Tokens.RPAREN); enableComments(obj); addComments(obj); } @Override public void visit(Query obj) { // Turn off adding comments until the query is completely formed disableComments(obj); addCacheHint(obj.getCacheHint()); addWithClause(obj); append(SELECT); SourceHint sh = obj.getSourceHint(); addSourceHint(sh); if (obj.getSelect() != null) { visitNode(obj.getSelect()); } if (obj.getInto() != null) { beginClause(1); visitNode(obj.getInto()); } if (obj.getFrom() != null) { beginClause(1); visitNode(obj.getFrom()); } // Where clause if (obj.getCriteria() != null) { beginClause(1); visitCriteria(WHERE, obj.getCriteria()); } // Group by clause if (obj.getGroupBy() != null) { beginClause(1); visitNode(obj.getGroupBy()); } // Having clause if (obj.getHaving() != null) { beginClause(1); visitCriteria(HAVING, obj.getHaving()); } // Order by clause if (obj.getOrderBy() != null) { beginClause(1); visitNode(obj.getOrderBy()); } if (obj.getLimit() != null) { beginClause(1); visitNode(obj.getLimit()); } // Option clause if (obj.getOption() != null) { beginClause(1); visitNode(obj.getOption()); } enableComments(obj); addComments(obj); } private void addSourceHint(SourceHint sh) { if (sh == null) return; append(SPACE); append(BEGIN_HINT); append("sh"); //$NON-NLS-1$ if (sh.isUseAliases()) { append(SPACE); append("KEEP ALIASES"); //$NON-NLS-1$ } if (sh.getGeneralHint() != null) { appendSourceHintValue(sh.getGeneralHint()); } else { append(SPACE); } if (sh.getSpecificHints() != null) { for (Map.Entry<String, SpecificHint> entry : sh.getSpecificHints().entrySet()) { append(entry.getKey()); if (entry.getValue().isUseAliases()) { append(SPACE); append("KEEP ALIASES"); //$NON-NLS-1$ } appendSourceHintValue(entry.getValue().getHint()); } } append(END_HINT); } private void addWithClause(QueryCommand obj) { if (obj.getWith() != null) { append(WITH); append(SPACE); registerNodes(obj.getWith(), 0); beginClause(0); } } protected void visitCriteria(String keyWord, Criteria crit) { append(keyWord); append(SPACE); visitNode(crit); } @Override public void visit(SearchedCaseExpression obj) { // Turn off adding comments until the query is completely formed disableComments(obj); append(CASE); for (int i = 0; i < obj.getWhenCount(); i++) { append(SPACE); append(WHEN); append(SPACE); visitNode(obj.getWhenCriteria(i)); append(SPACE); append(THEN); append(SPACE); visitNode(obj.getThenExpression(i)); } append(SPACE); if (obj.getElseExpression() != null) { append(ELSE); append(SPACE); visitNode(obj.getElseExpression()); append(SPACE); } append(END); enableComments(obj); addComments(obj); } @Override public void visit(Select obj) { // Turn off adding comments until the query is completely formed disableComments(obj); if (obj.isDistinct()) { append(SPACE); append(DISTINCT); } beginClause(2); Iterator<Expression> iter = obj.getSymbols().iterator(); while (iter.hasNext()) { Expression symbol = iter.next(); visitNode(symbol); if (iter.hasNext()) { append(", "); //$NON-NLS-1$ } } enableComments(obj); addComments(obj); } private void appendSourceHintValue(String sh) { append(Tokens.COLON); append('\''); append(escapeStringValue(sh, "'")); //$NON-NLS-1$ append('\''); append(SPACE); } @Override public void visit(SetCriteria obj) { // Turn off adding comments until the query is completely formed disableComments(obj); // variable appendNested(obj.getExpression()); // operator and beginning of list append(SPACE); if (obj.isNegated()) { append(NOT); append(SPACE); } append(IN); append(" ("); //$NON-NLS-1$ // value list Collection vals = obj.getValues(); int size = vals.size(); if (size == 1) { Iterator iter = vals.iterator(); Expression expr = (Expression)iter.next(); visitNode(expr); } else if (size > 1) { Iterator iter = vals.iterator(); Expression expr = (Expression)iter.next(); visitNode(expr); while (iter.hasNext()) { expr = (Expression)iter.next(); append(", "); //$NON-NLS-1$ visitNode(expr); } } append(")"); //$NON-NLS-1$ enableComments(obj); addComments(obj); } /** * Condition operators have lower precedence than LIKE/SIMILAR/IS * @param ex */ private void appendNested(Expression ex) { boolean useParens = ex instanceof Criteria; if (useParens) { append(Tokens.LPAREN); } visitNode(ex); if (useParens) { append(Tokens.RPAREN); } } @Override public void visit(SetQuery obj) { // Turn off adding comments until the query is completely formed disableComments(obj); addCacheHint(obj.getCacheHint()); addWithClause(obj); QueryCommand query = obj.getLeftQuery(); appendSetQuery(obj, query, false); beginClause(0); append(obj.getOperation()); if (obj.isAll()) { append(SPACE); append(ALL); } beginClause(0); query = obj.getRightQuery(); appendSetQuery(obj, query, true); if (obj.getOrderBy() != null) { beginClause(0); visitNode(obj.getOrderBy()); } if (obj.getLimit() != null) { beginClause(0); visitNode(obj.getLimit()); } if (obj.getOption() != null) { beginClause(0); visitNode(obj.getOption()); } enableComments(obj); addComments(obj); } protected void appendSetQuery(SetQuery parent, QueryCommand obj, boolean right) { if(shouldNestSetChild(parent, obj, right)) { append(Tokens.LPAREN); visitNode(obj); append(Tokens.RPAREN); } else { visitNode(obj); } enableComments(obj); addComments(obj); } protected boolean shouldNestSetChild(SetQuery parent, QueryCommand obj, boolean right) { return (!(obj instanceof SetQuery) && useParensForSetQueries()) || (!useSelectLimit() && (obj.getLimit() != null || obj.getOrderBy() != null)) || (right && ((obj instanceof SetQuery && ((parent.isAll() && !((SetQuery)obj).isAll()) || parent.getOperation() != ((SetQuery)obj).getOperation())) || obj.getLimit() != null || obj.getOrderBy() != null)); } protected boolean useSelectLimit() { return false; } protected boolean useParensForSetQueries() { return false; } @Override public void visit(StoredProcedure obj) { // Turn off adding comments until the query is completely formed disableComments(obj); addCacheHint(obj.getCacheHint()); if (obj.isCalledWithReturn()) { for (SPParameter param : obj.getParameters()) { if (param.getParameterType() == SPParameter.RETURN_VALUE) { if (param.getExpression() == null) { append("?"); //$NON-NLS-1$ } else { visitNode(param.getExpression()); } } } append(SPACE); append(Tokens.EQ); append(SPACE); } // exec clause append(EXEC); append(SPACE); append(obj.getProcedureName()); append("("); //$NON-NLS-1$ boolean first = true; for (SPParameter param : obj.getParameters()) { if (param.isUsingDefault() || param.getParameterType() == SPParameter.RETURN_VALUE || param.getParameterType() == SPParameter.RESULT_SET || param.getExpression() == null) { continue; } if (first) { first = false; } else { append(", "); //$NON-NLS-1$ } if (obj.isDisplayNamedParameters()) { append(escapeSinglePart(Symbol.getShortName(param.getParameterSymbol().getOutputName()))); append(" => "); //$NON-NLS-1$ } boolean addParens = !obj.isDisplayNamedParameters() && param.getExpression() instanceof CompareCriteria; if (addParens) { append(Tokens.LPAREN); } visitNode(param.getExpression()); if (addParens) { append(Tokens.RPAREN); } } append(")"); //$NON-NLS-1$ // Option clause if (obj.getOption() != null) { beginClause(1); visitNode(obj.getOption()); } enableComments(obj); addComments(obj); } /** * @param obj */ public void addCacheHint( CacheHint obj ) { if (obj == null) { return; } append(BEGIN_HINT); append(SPACE); append(CacheHint.CACHE); boolean addParens = false; if (obj.isPrefersMemory()) { append(Tokens.LPAREN); addParens = true; append(CacheHint.PREF_MEM); } if (obj.getTtl() != null) { if (!addParens) { append(Tokens.LPAREN); addParens = true; } else { append(SPACE); } append(CacheHint.TTL); append(obj.getTtl()); } if (obj.getUpdatable() != null) { if (!addParens) { append(Tokens.LPAREN); addParens = true; } else { append(SPACE); } append(CacheHint.UPDATABLE); } if (obj.getScope() != null) { if (!addParens) { append(Tokens.LPAREN); addParens = true; } else { append(SPACE); } append(CacheHint.SCOPE); append(obj.getScope()); } if (obj.getMinRows() != null ) { if (!addParens) { append(Tokens.LPAREN); addParens = true; } else { append(SPACE); } append(CacheHint.MIN); append(obj.getMinRows()); } if (addParens) { append(Tokens.RPAREN); } append(SPACE); append(END_HINT); beginClause(0); } @Override public void visit(SubqueryFromClause obj) { // Turn off adding comments until the query is completely formed disableComments(obj); addHintComment(obj); if (obj.isLateral()) { append(LATERAL); } append("(");//$NON-NLS-1$ visitNode(obj.getCommand()); append(")");//$NON-NLS-1$ append(" AS ");//$NON-NLS-1$ GroupSymbol groupSymbol = obj.getGroupSymbol(); append(escapeSinglePart(groupSymbol.getOutputName())); enableComments(obj); addComments(obj); } @Override public void visit(SubquerySetCriteria obj) { // Turn off adding comments until the query is completely formed disableComments(obj); // variable visitNode(obj.getExpression()); // operator and beginning of list append(SPACE); if (obj.isNegated()) { append(NOT); append(SPACE); } append(IN); addSubqueryHint(obj.getSubqueryHint()); append(" ("); //$NON-NLS-1$ visitNode(obj.getCommand()); append(")"); //$NON-NLS-1$ enableComments(obj); addComments(obj); } @Override public void visit(UnaryFromClause obj) { // Turn off adding comments until the query is completely formed disableComments(obj); addHintComment(obj); if (obj.getExpandedCommand() != null) { append("(");//$NON-NLS-1$ visitNode(obj.getExpandedCommand()); append(")");//$NON-NLS-1$ append(" AS ");//$NON-NLS-1$ append(escapeSinglePart(obj.getGroup().getName())); } else { visitNode(obj.getGroup()); } enableComments(obj); addComments(obj); } @Override public void visit(Update obj) { // Turn off adding comments until the query is completely formed disableComments(obj); // Update clause append(UPDATE); addSourceHint(obj.getSourceHint()); append(SPACE); visitNode(obj.getGroup()); beginClause(1); // Set clause append(SET); beginClause(2); visitNode(obj.getChangeList()); // Where clause if (obj.getCriteria() != null) { beginClause(1); visitCriteria(WHERE, obj.getCriteria()); } // Option clause if (obj.getOption() != null) { beginClause(1); visitNode(obj.getOption()); } enableComments(obj); addComments(obj); } @Override public void visit(Into obj) { // Turn off adding comments until the query is completely formed disableComments(obj); append(INTO); append(SPACE); visitNode(obj.getGroup()); enableComments(obj); addComments(obj); } // ############ Visitor methods for symbol objects #################### @Override public void visit(AggregateSymbol obj) { // Turn off adding comments until the query is completely formed disableComments(obj); append(obj.getName()); append("("); //$NON-NLS-1$ if (obj.isDistinct()) { append(DISTINCT); append(" "); //$NON-NLS-1$ } else if (obj.getAggregateFunction() == Type.USER_DEFINED) { append(ALL); append(" "); //$NON-NLS-1$ } if (obj.getArgs() == null || obj.getArgs().length == 0 ) { if (obj.getAggregateFunction() == Type.COUNT) { append(Tokens.ALL_COLS); } } else { registerNodes(obj.getArgs(), 0); } if (obj.getOrderBy() != null) { append(SPACE); visitNode(obj.getOrderBy()); } append(")"); //$NON-NLS-1$ if (obj.getCondition() != null) { append(SPACE); append(FILTER); append(Tokens.LPAREN); append(WHERE); append(SPACE); append(obj.getCondition()); append(Tokens.RPAREN); } enableComments(obj); addComments(obj); } @Override public void visit(AliasSymbol obj) { // Turn off adding comments until the query is completely formed disableComments(obj); visitNode(obj.getSymbol()); append(SPACE); append(AS); append(SPACE); append(escapeSinglePart(obj.getOutputName())); enableComments(obj); addComments(obj); } @Override public void visit(MultipleElementSymbol obj) { // Turn off adding comments until the query is completely formed disableComments(obj); if (obj.getGroup() == null) { append(Tokens.ALL_COLS); } else { visitNode(obj.getGroup()); append(Tokens.DOT); append(Tokens.ALL_COLS); } enableComments(obj); addComments(obj); } private void outputLiteral(Class<?> type, boolean multiValued, Object value) throws AssertionError { String[] constantParts = null; if (multiValued) { constantParts = new String[] {"?"}; //$NON-NLS-1$ } else if (value == null) { if (type.equals(DataTypeManagerService.DefaultDataTypes.BOOLEAN.getTypeClass())) { constantParts = new String[] {UNKNOWN}; } else { constantParts = new String[] {"null"}; //$NON-NLS-1$ } } else { if (value.getClass() == ArrayImpl.class) { ArrayImpl av = (ArrayImpl)value; append(Tokens.LPAREN); for (int i = 0; i < av.getValues().length; i++) { if (i > 0) { append(Tokens.COMMA); append(SPACE); } Object value2 = av.getValues()[i]; outputLiteral(value2!=null?value2.getClass():av.getValues().getClass().getComponentType(), multiValued, value2); } // Added to support Teiid 8.9 if (av.getValues().length == 1 ) { append(Tokens.COMMA); } append(Tokens.RPAREN); return; } else if (type.isArray()) { // Added to support Teiid 8.9 append(Tokens.LPAREN); int length = java.lang.reflect.Array.getLength(value); for (int i = 0; i < length; i++) { if (i > 0) { append(Tokens.COMMA); append(SPACE); } Object value2 = java.lang.reflect.Array.get(value, i); outputLiteral(type.getComponentType(), multiValued, value2); } if (length == 1) { append(Tokens.COMMA); } append(Tokens.RPAREN); return; } if (Number.class.isAssignableFrom(type)) { constantParts = new String[] {value.toString()}; } else if (type.equals(DataTypeManagerService.DefaultDataTypes.BOOLEAN.getTypeClass())) { constantParts = new String[] {value.equals(Boolean.TRUE) ? TRUE : FALSE}; } else if (type.equals(DataTypeManagerService.DefaultDataTypes.TIMESTAMP.getTypeClass())) { constantParts = new String[] {"{ts'", value.toString(), "'}"}; //$NON-NLS-1$ //$NON-NLS-2$ } else if (type.equals(DataTypeManagerService.DefaultDataTypes.TIME.getTypeClass())) { constantParts = new String[] {"{t'", value.toString(), "'}"}; //$NON-NLS-1$ //$NON-NLS-2$ } else if (type.equals(DataTypeManagerService.DefaultDataTypes.DATE.getTypeClass())) { constantParts = new String[] {"{d'", value.toString(), "'}"}; //$NON-NLS-1$ //$NON-NLS-2$ } else if (type.equals(DataTypeManagerService.DefaultDataTypes.VARBINARY.getTypeClass())) { constantParts = new String[] {"X'", value.toString(), "'"}; //$NON-NLS-1$ //$NON-NLS-2$ } if (constantParts == null) { if ( DataTypeManagerService.DefaultDataTypes.isLOB(type) ) { constantParts = new String[] {"?"}; //$NON-NLS-1$ } else { append('\''); String strValue = value.toString(); for (int i = 0; i < strValue.length(); i++) { char c = strValue.charAt(i); if (c == '\'') { append('\''); } else if (Character.isISOControl(c)) { append("\\u" + PropertiesUtils.toHex((c >> 12) & 0xF) + PropertiesUtils.toHex((c >> 8) & 0xF) //$NON-NLS-1$ + PropertiesUtils.toHex((c >> 4) & 0xF) + PropertiesUtils.toHex(c & 0xF)); continue; } append(c); } append('\''); return; } } } for (String string : constantParts) { append(string); } } private void visit9(Constant obj) { Class<?> type = obj.getType(); boolean multiValued = obj.isMultiValued(); Object value = obj.getValue(); outputLiteral(type, multiValued, value); } @Override public void visit(Constant obj) { // Turn off adding comments until the query is completely formed disableComments(obj); visit9(obj); enableComments(obj); addComments(obj); } /** * Take a string literal and escape it as necessary. By default, this converts ' to ''. * * @param str String literal value (unquoted), never null * @return Escaped string literal value */ static String escapeStringValue(String str, String tick) { return StringUtil.replaceAll(str, tick, tick + tick); } @Override public void visit(ElementSymbol obj) { // Turn off adding comments until the query is completely formed disableComments(obj); if (obj.getDisplayMode().equals(ElementSymbol.DisplayMode.SHORT_OUTPUT_NAME) ||isShortNameOnly()) { outputShortName(obj); enableComments(obj); addComments(obj); return; } String name = obj.getOutputName(); if (obj.getDisplayMode().equals(ElementSymbol.DisplayMode.FULLY_QUALIFIED)) { name = obj.getName(); } outputDisplayName(name); enableComments(obj); addComments(obj); } private void outputShortName(ElementSymbol obj) { outputDisplayName(Symbol.getShortName(obj.getOutputName())); } private void outputDisplayName(String name) { String[] pathParts = name.split("\\."); //$NON-NLS-1$ for (int i = 0; i < pathParts.length; i++) { if (i > 0) { append(Symbol.SEPARATOR); } append(escapeSinglePart(pathParts[i])); } } @Override public void visit(ExpressionSymbol obj) { // Turn off adding comments until the query is completely formed disableComments(obj); visitNode(obj.getExpression()); enableComments(obj); addComments(obj); } @Override public void visit(Function obj) { // Turn off adding comments until the query is completely formed disableComments(obj); String name = obj.getName(); Expression[] args = obj.getArgs(); if (obj.isImplicit()) { // Hide this function, which is implicit visitNode(args[0]); } else if (name.equalsIgnoreCase(CONVERT) || name.equalsIgnoreCase(CAST) || name.equalsIgnoreCase(XMLCAST)) { append(name); append("("); //$NON-NLS-1$ if (args != null && args.length > 0) { visitNode(args[0]); if (name.equalsIgnoreCase(CONVERT)) { append(", "); //$NON-NLS-1$ } else { append(" "); //$NON-NLS-1$ append(AS); append(" "); //$NON-NLS-1$ } if (args.length < 2 || args[1] == null || !(args[1] instanceof Constant)) { append(UNDEFINED); } else { append(((Constant)args[1]).getValue()); } } append(")"); //$NON-NLS-1$ } else if (INFIX_FUNCTIONS.contains(name)) { append("("); //$NON-NLS-1$ if (args != null) { for (int i = 0; i < args.length; i++) { visitNode(args[i]); if (i < (args.length - 1)) { append(SPACE); append(name); append(SPACE); } } } append(")"); //$NON-NLS-1$ } else if (name.equalsIgnoreCase(NonReserved.TIMESTAMPADD) || name.equalsIgnoreCase(NonReserved.TIMESTAMPDIFF)) { append(name); append("("); //$NON-NLS-1$ if (args != null && args.length > 0) { append(((Constant)args[0]).getValue()); registerNodes(args, 1); } append(")"); //$NON-NLS-1$ } else if (name.equalsIgnoreCase(SourceSystemFunctions.XMLPI)) { append(name); append("(NAME "); //$NON-NLS-1$ outputDisplayName((String)((Constant)args[0]).getValue()); registerNodes(args, 1); append(")"); //$NON-NLS-1$ } else if (name.equalsIgnoreCase(SourceSystemFunctions.TRIM)) { append(name); append(SQLConstants.Tokens.LPAREN); String value = (String)((Constant)args[0]).getValue(); if (!value.equalsIgnoreCase(BOTH)) { append(((Constant)args[0]).getValue()); append(" "); //$NON-NLS-1$ } append(args[1]); append(" "); //$NON-NLS-1$ append(FROM); append(" "); //$NON-NLS-1$ append(args[2]); append(")"); //$NON-NLS-1$ } else { append(name); append("("); //$NON-NLS-1$ registerNodes(args, 0); append(")"); //$NON-NLS-1$ } enableComments(obj); addComments(obj); } private void registerNodes(LanguageObject[] objects, int begin) { registerNodes(Arrays.asList(objects), begin); } private void registerNodes(List<? extends LanguageObject> objects, int begin) { if (objects == null) return; for (int i = begin; i < objects.size(); i++) { if (i > 0) { append(", "); //$NON-NLS-1$ } visitNode(objects.get(i)); } } @Override public void visit(GroupSymbol obj) { // Turn off adding comments until the query is completely formed disableComments(obj); String alias = null; String fullGroup = obj.getOutputName(); if (obj.getOutputDefinition() != null) { alias = obj.getOutputName(); fullGroup = obj.getOutputDefinition(); } outputDisplayName(fullGroup); if (alias != null) { append(SPACE); append(AS); append(SPACE); append(escapeSinglePart(alias)); } enableComments(obj); addComments(obj); } @Override public void visit(Reference obj) { // Turn off adding comments until the query is completely formed disableComments(obj); if (!obj.isPositional() && obj.getExpression() != null) { visitNode(obj.getExpression()); } else { append("?"); //$NON-NLS-1$ } enableComments(obj); addComments(obj); } // ############ Visitor methods for storedprocedure language objects #################### private void addStatements(List<Statement> statements) { Iterator<Statement> stmtIter = statements.iterator(); while (stmtIter.hasNext()) { // Add each statement addTabs(1); visitNode(stmtIter.next()); append("\n"); //$NON-NLS-1$ } addTabs(0); } private void visit9(Block obj) { addLabel(obj); List<Statement> statements = obj.getStatements(); // Add first clause append(BEGIN); if (obj.isAtomic()) { append(SPACE); append(ATOMIC); } append("\n"); //$NON-NLS-1$ addStatements(statements); if (obj.getExceptionGroup() != null) { append(NonReserved.EXCEPTION); append(SPACE); outputDisplayName(obj.getExceptionGroup()); append("\n"); //$NON-NLS-1$ if (obj.getExceptionStatements() != null) { addStatements(obj.getExceptionStatements()); } } append(END); } @Override public void visit(Block block) { // Turn off adding comments until the query is completely formed disableComments(block); visit9(block); enableComments(block); addComments(block); } private void addLabel(Labeled obj) { if (obj.getLabel() != null) { outputDisplayName(obj.getLabel()); append(SPACE); append(Tokens.COLON); append(SPACE); } } /** * @param level */ protected void addTabs(int level) { // Do nothing } @Override public void visit(CommandStatement obj) { // Turn off adding comments until the query is completely formed disableComments(obj); visitNode(obj.getCommand()); if (!obj.isReturnable()) { append(SPACE); append(WITHOUT); append(SPACE); append(RETURN); } append(";"); //$NON-NLS-1$ enableComments(obj); addComments(obj); } @Override public void visit(CreateProcedureCommand obj) { addCacheHint(obj.getCacheHint()); // Turn off adding comments until the query is completely formed disableComments(obj); visitNode(obj.getBlock()); enableComments(obj); addComments(obj); } @Override public void visit(DeclareStatement obj) { // Turn off adding comments until the query is completely formed disableComments(obj); append(DECLARE); append(SPACE); append(obj.getVariableType()); append(SPACE); createAssignment(obj); enableComments(obj); addComments(obj); } /** * @param obj * @param parts */ private void createAssignment(AssignmentStatement obj) { visitNode(obj.getVariable()); if (obj.getExpression() != null) { append(" = "); //$NON-NLS-1$ visitNode(obj.getExpression()); } append(";"); //$NON-NLS-1$ enableComments(obj); addComments(obj); } @Override public void visit(IfStatement obj) { // Turn off adding comments until the query is completely formed disableComments(obj); append(IF); append("("); //$NON-NLS-1$ visitNode(obj.getCondition()); append(")\n"); //$NON-NLS-1$ addTabs(0); visitNode(obj.getIfBlock()); if (obj.hasElseBlock()) { append("\n"); //$NON-NLS-1$ addTabs(0); append(ELSE); append("\n"); //$NON-NLS-1$ addTabs(0); visitNode(obj.getElseBlock()); } enableComments(obj); addComments(obj); } @Override public void visit(AssignmentStatement obj) { // Turn off adding comments until the query is completely formed disableComments(obj); createAssignment(obj); enableComments(obj); addComments(obj); } @Override public void visit(RaiseStatement obj) { // Turn off adding comments until the query is completely formed disableComments(obj); append(NonReserved.RAISE); append(SPACE); if (obj.isWarning()) { append(SQLWARNING); append(SPACE); } visitNode(obj.getExpression()); append(";"); //$NON-NLS-1$ enableComments(obj); addComments(obj); } @Override public void visit(RaiseErrorStatement obj) { // Turn off adding comments until the query is completely formed disableComments(obj); append(ERROR); append(SPACE); visitNode(obj.getExpression()); append(";"); //$NON-NLS-1$ enableComments(obj); addComments(obj); } @Override public void visit(ExceptionExpression exceptionExpression) { // Turn off adding comments until the query is completely formed disableComments(exceptionExpression); append(SQLEXCEPTION); append(SPACE); visitNode(exceptionExpression.getMessage()); if (exceptionExpression.getSqlState() != null) { append(SPACE); append(SQLSTATE); append(SPACE); append(exceptionExpression.getSqlState()); if (exceptionExpression.getErrorCode() != null) { append(Tokens.COMMA); append(SPACE); append(exceptionExpression.getErrorCode()); } } if (exceptionExpression.getParent() != null) { append(SPACE); append(NonReserved.CHAIN); append(SPACE); append(exceptionExpression.getParent()); } enableComments(exceptionExpression); addComments(exceptionExpression); } @Override public void visit(ReturnStatement obj) { // Turn off adding comments until the query is completely formed disableComments(obj); append(RETURN); if (obj.getExpression() != null) { append(SPACE); visitNode(obj.getExpression()); } append(Tokens.SEMICOLON); enableComments(obj); addComments(obj); } @Override public void visit(BranchingStatement obj) { // Turn off adding comments until the query is completely formed disableComments(obj); switch (obj.getMode()) { case CONTINUE: append(CONTINUE); break; case BREAK: append(BREAK); break; case LEAVE: append(LEAVE); break; } if (obj.getLabel() != null) { append(SPACE); outputDisplayName(obj.getLabel()); } append(";"); //$NON-NLS-1$ enableComments(obj); addComments(obj); } @Override public void visit(LoopStatement obj) { // Turn off adding comments until the query is completely formed disableComments(obj); addLabel(obj); append(LOOP); append(" "); //$NON-NLS-1$ append(ON); append(" ("); //$NON-NLS-1$ visitNode(obj.getCommand()); append(") "); //$NON-NLS-1$ append(AS); append(" "); //$NON-NLS-1$ outputDisplayName(obj.getCursorName()); append("\n"); //$NON-NLS-1$ addTabs(0); visitNode(obj.getBlock()); enableComments(obj); addComments(obj); } @Override public void visit(WhileStatement obj) { // Turn off adding comments until the query is completely formed disableComments(obj); addLabel(obj); append(WHILE); append("("); //$NON-NLS-1$ visitNode(obj.getCondition()); append(")\n"); //$NON-NLS-1$ addTabs(0); visitNode(obj.getBlock()); enableComments(obj); addComments(obj); } @Override public void visit(ExistsCriteria obj) { // Turn off adding comments until the query is completely formed disableComments(obj); if (obj.isNegated()) { append(NOT); append(SPACE); } append(EXISTS); addSubqueryHint(obj.getSubqueryHint()); append(" ("); //$NON-NLS-1$ visitNode(obj.getCommand()); append(")"); //$NON-NLS-1$ enableComments(obj); addComments(obj); } private void addSubqueryHint(SubqueryHint hint) { if (hint.isNoUnnest()) { append(SPACE); append(BEGIN_HINT); append(SPACE); append(SubqueryHint.NOUNNEST); append(SPACE); append(END_HINT); } else if (hint.isDepJoin()) { append(SPACE); append(BEGIN_HINT); append(SPACE); append(SubqueryHint.DJ); append(SPACE); append(END_HINT); } else if (hint.isMergeJoin()) { append(SPACE); append(BEGIN_HINT); append(SPACE); append(SubqueryHint.MJ); append(SPACE); append(END_HINT); } } @Override public void visit(SubqueryCompareCriteria obj) { // Turn off adding comments until the query is completely formed disableComments(obj); Expression leftExpression = obj.getLeftExpression(); visitNode(leftExpression); String operator = obj.getOperatorAsString(); String quantifier = obj.getPredicateQuantifierAsString(); // operator and beginning of list append(SPACE); append(operator); append(SPACE); append(quantifier); addSubqueryHint(obj.getSubqueryHint()); append(SPACE); append("("); //$NON-NLS-1$ if (obj.getCommand() != null) { visitNode(obj.getCommand()); } else { visitNode(obj.getArrayExpression()); } append(")"); //$NON-NLS-1$ enableComments(obj); addComments(obj); } @Override public void visit(ScalarSubquery obj) { // Turn off adding comments until the query is completely formed disableComments(obj); if (obj.getSubqueryHint().isDepJoin() || obj.getSubqueryHint().isMergeJoin() || obj.getSubqueryHint().isNoUnnest()) { if (this.parts.charAt(this.parts.length() - 1) == ' ') { this.parts.setLength(this.parts.length() - 1); } addSubqueryHint(obj.getSubqueryHint()); append(SPACE); } // operator and beginning of list append("("); //$NON-NLS-1$ visitNode(obj.getCommand()); append(")"); //$NON-NLS-1$ enableComments(obj); addComments(obj); } @Override public void visit(XMLAttributes obj) { // Turn off adding comments until the query is completely formed disableComments(obj); append(XMLATTRIBUTES); append("("); //$NON-NLS-1$ registerNodes(obj.getArgs(), 0); append(")"); //$NON-NLS-1$ enableComments(obj); addComments(obj); } @Override public void visit(XMLElement obj) { // Turn off adding comments until the query is completely formed disableComments(obj); append(XMLELEMENT); append("(NAME "); //$NON-NLS-1$ outputDisplayName(obj.getName()); if (obj.getNamespaces() != null) { append(", "); //$NON-NLS-1$ visitNode(obj.getNamespaces()); } if (obj.getAttributes() != null) { append(", "); //$NON-NLS-1$ visitNode(obj.getAttributes()); } if (!obj.getContent().isEmpty()) { append(", "); //$NON-NLS-1$ } registerNodes(obj.getContent(), 0); append(")"); //$NON-NLS-1$ enableComments(obj); addComments(obj); } @Override public void visit(XMLForest obj) { // Turn off adding comments until the query is completely formed disableComments(obj); append(XMLFOREST); append("("); //$NON-NLS-1$ if (obj.getNamespaces() != null) { visitNode(obj.getNamespaces()); append(", "); //$NON-NLS-1$ } registerNodes(obj.getArgs(), 0); append(")"); //$NON-NLS-1$ enableComments(obj); addComments(obj); } @Override public void visit(JSONObject obj) { // Turn off adding comments until the query is completely formed disableComments(obj); append(NonReserved.JSONOBJECT); append("("); //$NON-NLS-1$ registerNodes(obj.getArgs(), 0); append(")"); //$NON-NLS-1$ enableComments(obj); addComments(obj); } @Override public void visit(TextLine obj) { // Turn off adding comments until the query is completely formed disableComments(obj); append(FOR); append(SPACE); registerNodes(obj.getExpressions(), 0); if (obj.getDelimiter() != null) { append(SPACE); append(NonReserved.DELIMITER); append(SPACE); visitNode(newConstant(obj.getDelimiter())); } if (obj.getQuote() != null) { append(SPACE); append(NonReserved.QUOTE); append(SPACE); visitNode(newConstant(obj.getQuote())); } if (obj.isIncludeHeader()) { append(SPACE); append(NonReserved.HEADER); } if (obj.getEncoding() != null) { append(SPACE); append(NonReserved.ENCODING); append(SPACE); outputDisplayName(obj.getEncoding()); } enableComments(obj); addComments(obj); } @Override public void visit(XMLNamespaces obj) { // Turn off adding comments until the query is completely formed disableComments(obj); append(XMLNAMESPACES); append("("); //$NON-NLS-1$ for (Iterator<NamespaceItem> items = obj.getNamespaceItems().iterator(); items.hasNext();) { NamespaceItem item = items.next(); if (item.getPrefix() == null) { if (item.getUri() == null) { append("NO DEFAULT"); //$NON-NLS-1$ } else { append("DEFAULT "); //$NON-NLS-1$ visitNode(newConstant(item.getUri())); } } else { visitNode(newConstant(item.getUri())); append(" AS "); //$NON-NLS-1$ outputDisplayName(item.getPrefix()); } if (items.hasNext()) { append(", "); //$NON-NLS-1$ } } append(")"); //$NON-NLS-1$ enableComments(obj); addComments(obj); } @Override public void visit(Limit obj) { // Turn off adding comments until the query is completely formed disableComments(obj); if (!obj.isStrict()) { append(BEGIN_HINT); append(SPACE); append(Limit.NON_STRICT); append(SPACE); append(END_HINT); append(SPACE); } if (obj.getRowLimit() == null) { append(OFFSET); append(SPACE); visitNode(obj.getOffset()); append(SPACE); append(ROWS); enableComments(obj); addComments(obj); return; } append(LIMIT); if (obj.getOffset() != null) { append(SPACE); visitNode(obj.getOffset()); append(","); //$NON-NLS-1$ } append(SPACE); visitNode(obj.getRowLimit()); enableComments(obj); addComments(obj); } @Override public void visit(TextTable obj) { addHintComment(obj); // Turn off adding comments until the query is completely formed disableComments(obj); append("TEXTTABLE("); //$NON-NLS-1$ visitNode(obj.getFile()); if (obj.getSelector() != null) { append(SPACE); append(NonReserved.SELECTOR); append(SPACE); append(escapeSinglePart(obj.getSelector())); } append(SPACE); append(NonReserved.COLUMNS); boolean noTrim = obj.isNoTrim(); for (Iterator<TextColumn> cols = obj.getColumns().iterator(); cols.hasNext();) { TextColumn col = cols.next(); append(SPACE); outputDisplayName(col.getName()); append(SPACE); if (col.isOrdinal()) { // Will only ever come in here is Teiid 8.7 or greater append(FOR); append(SPACE); append(NonReserved.ORDINALITY); } else { if (col.getHeader() != null) { append(NonReserved.HEADER); append(SPACE); outputLiteral(String.class, false, col.getHeader()); append(SPACE); } append(col.getType()); if (col.getWidth() != null) { append(SPACE); append(NonReserved.WIDTH); append(SPACE); append(col.getWidth()); } if (!noTrim && col.isNoTrim()) { append(SPACE); append(NO); append(SPACE); append(NonReserved.TRIM); } if (col.getSelector() != null) { append(SPACE); append(NonReserved.SELECTOR); append(SPACE); append(escapeSinglePart(col.getSelector())); append(SPACE); append(col.getPosition()); } } if (cols.hasNext()) { append(","); //$NON-NLS-1$ } } if (!obj.isUsingRowDelimiter()) { append(SPACE); append(NO); append(SPACE); append(ROW); append(SPACE); append(NonReserved.DELIMITER); } else if (obj.getRowDelimiter() != null) { append(SPACE); append(ROW); append(SPACE); append(NonReserved.DELIMITER); append(SPACE); Constant constant = createASTNode(ASTNodes.CONSTANT); constant.setValue(obj.getRowDelimiter()); visitNode(constant); } if (obj.getDelimiter() != null) { append(SPACE); append(NonReserved.DELIMITER); append(SPACE); visitNode(newConstant(obj.getDelimiter())); } if (obj.getQuote() != null) { append(SPACE); if (obj.isEscape()) { append(ESCAPE); } else { append(NonReserved.QUOTE); } append(SPACE); visitNode(newConstant(obj.getQuote())); } if (obj.getHeader() != null) { append(SPACE); append(NonReserved.HEADER); if (1 != obj.getHeader()) { append(SPACE); append(obj.getHeader()); } } if (obj.getSkip() != null) { append(SPACE); append(NonReserved.SKIP); append(SPACE); append(obj.getSkip()); } if (noTrim) { append(SPACE); append(NO); append(SPACE); append(NonReserved.TRIM); } append(")");//$NON-NLS-1$ append(SPACE); append(AS); append(SPACE); outputDisplayName(obj.getName()); enableComments(obj); addComments(obj); } @Override public void visit(XMLTable obj) { addHintComment(obj); // Turn off adding comments until the query is completely formed disableComments(obj); append("XMLTABLE("); //$NON-NLS-1$ if (obj.getNamespaces() != null) { visitNode(obj.getNamespaces()); append(","); //$NON-NLS-1$ append(SPACE); } visitNode(newConstant(obj.getXquery())); if (!obj.getPassing().isEmpty()) { append(SPACE); append(NonReserved.PASSING); append(SPACE); registerNodes(obj.getPassing(), 0); } if (!obj.getColumns().isEmpty() && !obj.isUsingDefaultColumn()) { append(SPACE); append(NonReserved.COLUMNS); for (Iterator<XMLColumn> cols = obj.getColumns().iterator(); cols.hasNext();) { XMLColumn col = cols.next(); append(SPACE); outputDisplayName(col.getName()); append(SPACE); if (col.isOrdinal()) { append(FOR); append(SPACE); append(NonReserved.ORDINALITY); } else { append(col.getType()); if (col.getDefaultExpression() != null) { append(SPACE); append(DEFAULT); append(SPACE); visitNode(col.getDefaultExpression()); } if (col.getPath() != null) { append(SPACE); append(NonReserved.PATH); append(SPACE); visitNode(newConstant(col.getPath())); } } if (cols.hasNext()) { append(","); //$NON-NLS-1$ } } } append(")");//$NON-NLS-1$ append(SPACE); append(AS); append(SPACE); outputDisplayName(obj.getName()); enableComments(obj); addComments(obj); } @Override public void visit(ObjectTable obj) { addHintComment(obj); // Turn off adding comments until the query is completely formed disableComments(obj); append("OBJECTTABLE("); //$NON-NLS-1$ if (obj.getScriptingLanguage() != null) { append(LANGUAGE); append(SPACE); visitNode(newConstant(obj.getScriptingLanguage())); append(SPACE); } visitNode(newConstant(obj.getRowScript())); if (!obj.getPassing().isEmpty()) { append(SPACE); append(NonReserved.PASSING); append(SPACE); registerNodes(obj.getPassing(), 0); } append(SPACE); append(NonReserved.COLUMNS); for (Iterator<ObjectColumn> cols = obj.getColumns().iterator(); cols.hasNext();) { ObjectColumn col = cols.next(); append(SPACE); outputDisplayName(col.getName()); append(SPACE); append(col.getType()); append(SPACE); visitNode(newConstant(col.getPath())); if (col.getDefaultExpression() != null) { append(SPACE); append(DEFAULT); append(SPACE); visitNode(col.getDefaultExpression()); } if (cols.hasNext()) { append(","); //$NON-NLS-1$ } } append(")");//$NON-NLS-1$ append(SPACE); append(AS); append(SPACE); outputDisplayName(obj.getName()); enableComments(obj); addComments(obj); } @Override public void visit(XMLQuery obj) { // Turn off adding comments until the query is completely formed disableComments(obj); append("XMLQUERY("); //$NON-NLS-1$ if (obj.getNamespaces() != null) { visitNode(obj.getNamespaces()); append(","); //$NON-NLS-1$ append(SPACE); } visitNode(newConstant(obj.getXquery())); if (!obj.getPassing().isEmpty()) { append(SPACE); append(NonReserved.PASSING); append(SPACE); registerNodes(obj.getPassing(), 0); } if (obj.getEmptyOnEmpty() != null) { append(SPACE); if (obj.getEmptyOnEmpty()) { append(NonReserved.EMPTY); } else { append(NULL); } append(SPACE); append(ON); append(SPACE); append(NonReserved.EMPTY); } append(")");//$NON-NLS-1$ enableComments(obj); addComments(obj); } @Override public void visit(XMLExists exists) { // Turn off adding comments until the query is completely formed disableComments(exists); append("XMLEXISTS("); //$NON-NLS-1$ XMLQuery obj = exists.getXmlQuery(); if (obj.getNamespaces() != null) { visitNode(obj.getNamespaces()); append(","); //$NON-NLS-1$ append(SPACE); } Constant constant = createASTNode(ASTNodes.CONSTANT); constant.setValue(obj.getXquery()); visitNode(constant); if (!obj.getPassing().isEmpty()) { append(SPACE); append(NonReserved.PASSING); append(SPACE); registerNodes(obj.getPassing(), 0); } append(")");//$NON-NLS-1$ enableComments(exists); addComments(exists); } @Override public void visit(XMLCast exists) { // Turn off adding comments until the query is completely formed disableComments(exists); append("XMLCAST("); //$NON-NLS-1$ append(exists.getExpression()); append(Tokens.SPACE); append(AS); append(Tokens.SPACE); append(getDataTypeManager().getDataTypeName(exists.getType())); append(")");//$NON-NLS-1$ enableComments(exists); addComments(exists); } @Override public void visit(DerivedColumn obj) { // Turn off adding comments until the query is completely formed disableComments(obj); visitNode(obj.getExpression()); if (obj.getAlias() != null) { append(SPACE); append(AS); append(SPACE); outputDisplayName(obj.getAlias()); } enableComments(obj); addComments(obj); } @Override public void visit(XMLSerialize obj) { // Turn off adding comments until the query is completely formed disableComments(obj); append(XMLSERIALIZE); append(Tokens.LPAREN); if (obj.getDocument() != null) { if (obj.getDocument()) { append(NonReserved.DOCUMENT); } else { append(NonReserved.CONTENT); } append(SPACE); } visitNode(obj.getExpression()); if (obj.getTypeString() != null) { append(SPACE); append(AS); append(SPACE); append(obj.getTypeString()); } if (obj.getEncoding() != null) { append(SPACE); append(NonReserved.ENCODING); append(SPACE); append(escapeSinglePart(obj.getEncoding())); } if (obj.getVersion() != null) { append(SPACE); append(NonReserved.VERSION); append(SPACE); append(newConstant(obj.getVersion())); } if (obj.getDeclaration() != null) { append(SPACE); if (obj.getDeclaration()) { append(NonReserved.INCLUDING); } else { append(NonReserved.EXCLUDING); } append(SPACE); append(NonReserved.XMLDECLARATION); } append(Tokens.RPAREN); enableComments(obj); addComments(obj); } @Override public void visit(QueryString obj) { // Turn off adding comments until the query is completely formed disableComments(obj); append(NonReserved.QUERYSTRING); append("("); //$NON-NLS-1$ visitNode(obj.getPath()); if (!obj.getArgs().isEmpty()) { append(","); //$NON-NLS-1$ append(SPACE); registerNodes(obj.getArgs(), 0); } append(")"); //$NON-NLS-1$ enableComments(obj); addComments(obj); } @Override public void visit(XMLParse obj) { // Turn off adding comments until the query is completely formed disableComments(obj); append(XMLPARSE); append(Tokens.LPAREN); if (obj.isDocument()) { append(NonReserved.DOCUMENT); } else { append(NonReserved.CONTENT); } append(SPACE); visitNode(obj.getExpression()); if (obj.isWellFormed()) { append(SPACE); append(NonReserved.WELLFORMED); } append(Tokens.RPAREN); enableComments(obj); addComments(obj); } @Override public void visit(ExpressionCriteria obj) { // Turn off adding comments until the query is completely formed disableComments(obj); visitNode(obj.getExpression()); enableComments(obj); addComments(obj); } @Override public void visit(TriggerAction obj) { // Turn off adding comments until the query is completely formed disableComments(obj); append(FOR); append(SPACE); append(EACH); append(SPACE); append(ROW); append("\n"); //$NON-NLS-1$ addTabs(0); visitNode(obj.getBlock()); enableComments(obj); addComments(obj); } @Override public void visit(ArrayTable obj) { addHintComment(obj); // Turn off adding comments until the query is completely formed disableComments(obj); append("ARRAYTABLE("); //$NON-NLS-1$ visitNode(obj.getArrayValue()); append(SPACE); append(NonReserved.COLUMNS); for (Iterator<ProjectedColumn> cols = obj.getColumns().iterator(); cols.hasNext();) { ProjectedColumn col = cols.next(); append(SPACE); outputDisplayName(col.getName()); append(SPACE); append(col.getType()); if (cols.hasNext()) { append(","); //$NON-NLS-1$ } } append(")");//$NON-NLS-1$ append(SPACE); append(AS); append(SPACE); outputDisplayName(obj.getName()); enableComments(obj); addComments(obj); } private void addMakeDep(FromClause obj) { MakeDep makeDep = obj.getMakeDep(); if (makeDep != null && !makeDep.isSimple()) { append(SPACE); append(MAKEDEP); appendMakeDepOptions(makeDep); } makeDep = obj.getMakeInd(); if (makeDep != null && !makeDep.isSimple()) { append(SPACE); append(MAKEIND); appendMakeDepOptions(makeDep); } } private void visit9(AlterProcedure<CreateProcedureCommand> obj) { append(ALTER); append(SPACE); append(PROCEDURE); append(SPACE); append(obj.getTarget()); beginClause(1); append(AS); append(obj.getDefinition().getBlock()); } @Override public void visit(AlterProcedure obj) { // Turn off adding comments until the query is completely formed disableComments(obj); visit9(obj); enableComments(obj); addComments(obj); } @Override public void visit(AlterTrigger obj) { // Turn off adding comments until the query is completely formed disableComments(obj); if (obj.isCreate()) { append(CREATE); } else { append(ALTER); } append(SPACE); append(TRIGGER); append(SPACE); append(ON); append(SPACE); append(obj.getTarget()); beginClause(0); append(NonReserved.INSTEAD); append(SPACE); append(OF); append(SPACE); append(obj.getEvent()); if (obj.getDefinition() != null) { beginClause(0); append(AS); append("\n"); //$NON-NLS-1$ addTabs(0); append(obj.getDefinition()); } else { append(SPACE); append(obj.getEnabled() ? NonReserved.ENABLED : NonReserved.DISABLED); } enableComments(obj); addComments(obj); } @Override public void visit(AlterView obj) { // Turn off adding comments until the query is completely formed disableComments(obj); append(ALTER); append(SPACE); append(NonReserved.VIEW); append(SPACE); append(obj.getTarget()); beginClause(0); append(AS); append("\n"); //$NON-NLS-1$ addTabs(0); append(obj.getDefinition()); enableComments(obj); addComments(obj); } @Override public void visit(WindowFunction windowFunction) { // Turn off adding comments until the query is completely formed disableComments(windowFunction); append(windowFunction.getFunction()); append(SPACE); append(OVER); append(SPACE); append(windowFunction.getWindowSpecification()); enableComments(windowFunction); addComments(windowFunction); } @Override public void visit(WindowSpecification windowSpecification) { // Turn off adding comments until the query is completely formed disableComments(windowSpecification); append(Tokens.LPAREN); boolean needsSpace = false; if (windowSpecification.getPartition() != null) { append(PARTITION); append(SPACE); append(BY); append(SPACE); registerNodes(windowSpecification.getPartition(), 0); needsSpace = true; } if (windowSpecification.getOrderBy() != null) { if (needsSpace) { append(SPACE); } append(windowSpecification.getOrderBy()); } append(Tokens.RPAREN); enableComments(windowSpecification); addComments(windowSpecification); } @Override public void visit(Array array) { // Turn off adding comments until the query is completely formed disableComments(array); if (!array.isImplicit()) { append(Tokens.LPAREN); } registerNodes(array.getExpressions(), 0); if (!array.isImplicit()) { if (array.getExpressions().size() == 1) { append(Tokens.COMMA); } append(Tokens.RPAREN); } enableComments(array); addComments(array); } @Override public void visit(IsDistinctCriteria isDistinctCriteria) { append(isDistinctCriteria.getLeftRowValue()); append(SPACE); append(IS); append(SPACE); if (isDistinctCriteria.isNegated()) { append(NOT); append(SPACE); } append(DISTINCT); append(SPACE); append(FROM); append(SPACE); append(isDistinctCriteria.getRightRowValue()); } private String escapeSinglePart(String part) { if (isReservedWord(part)) { return ID_ESCAPE_CHAR + part + ID_ESCAPE_CHAR; } boolean escape = true; char start = part.charAt(0); if (start == '#' || start == '@' || StringUtil.isLetter(start)) { escape = false; for (int i = 1; !escape && i < part.length(); i++) { char c = part.charAt(i); escape = !StringUtil.isLetterOrDigit(c) && c != '_'; } } if (escape) { return ID_ESCAPE_CHAR + escapeStringValue(part, "\"") + ID_ESCAPE_CHAR; //$NON-NLS-1$ } return part; } /** * Check whether a string is considered a reserved word or not. Subclasses may override to change definition of reserved word. * * @param string String to check * @return True if reserved word */ private boolean isReservedWord(String string) { if (string == null) { return false; } return SQLConstants.isReservedWord(getTeiidVersion(), string); } }