/* * 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.language.visitor; import static org.teiid.language.SQLConstants.Reserved.*; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.teiid.connector.DataPlugin; import org.teiid.core.types.DataTypeManager; import org.teiid.core.util.StringUtil; import org.teiid.language.*; import org.teiid.language.Argument.Direction; import org.teiid.language.SQLConstants.NonReserved; import org.teiid.language.SQLConstants.Tokens; import org.teiid.language.SortSpecification.Ordering; import org.teiid.metadata.AbstractMetadataRecord; import org.teiid.metadata.Table; /** * Creates a SQL string for a LanguageObject subtree. Instances of this class * are not reusable, and are not thread-safe. */ public class SQLStringVisitor extends AbstractLanguageVisitor { public static final String TEIID_NATIVE_QUERY = AbstractMetadataRecord.RELATIONAL_URI + "native-query"; //$NON-NLS-1$ private Set<String> infixFunctions = new HashSet<String>(Arrays.asList("%", "+", "-", "*", "+", "/", "||", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ "&", "|", "^", "#", "&&")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ private static Pattern pattern = Pattern.compile("\\$+\\d+"); //$NON-NLS-1$ protected static final String UNDEFINED = "<undefined>"; //$NON-NLS-1$ protected static final String UNDEFINED_PARAM = "?"; //$NON-NLS-1$ protected StringBuilder buffer = new StringBuilder(); private boolean appendedSourceComment; protected boolean shortNameOnly = false; /** * Gets the name of a group or element from the RuntimeMetadata * @param id the id of the group or element * @return the name of that element or group as defined in the source */ protected String getName(AbstractMetadataRecord object) { return getRecordName(object); } /** * Get the name in source or the name if * the name in source is not set. * @return */ public static String getRecordName(AbstractMetadataRecord object) { return object.getSourceName(); } /** * Appends the string form of the LanguageObject to the current buffer. * @param obj the language object instance */ public void append(LanguageObject obj) { if (obj == null) { buffer.append(UNDEFINED); } else { visitNode(obj); } } /** * Simple utility to append a list of language objects to the current buffer * by creating a comma-separated list. * @param items a list of LanguageObjects */ protected void append(List<? extends LanguageObject> items) { if (items != null && items.size() != 0) { append(items.get(0)); for (int i = 1; i < items.size(); i++) { buffer.append(Tokens.COMMA) .append(Tokens.SPACE); append(items.get(i)); } } } /** * Simple utility to append an array of language objects to the current buffer * by creating a comma-separated list. * @param items an array of LanguageObjects */ protected void append(LanguageObject[] items) { if (items != null && items.length != 0) { append(items[0]); for (int i = 1; i < items.length; i++) { buffer.append(Tokens.COMMA) .append(Tokens.SPACE); append(items[i]); } } } /** * Creates a SQL-safe string. Simply replaces all occurrences of ' with '' * @param str the input string * @return a SQL-safe string */ protected String escapeString(String str, String quote) { return StringUtil.replaceAll(str, quote, quote + quote); } public String toString() { return buffer.toString(); } public void visit(AggregateFunction obj) { buffer.append(obj.getName()) .append(Tokens.LPAREN); if ( obj.isDistinct()) { buffer.append(DISTINCT) .append(Tokens.SPACE); } if (obj.getParameters().isEmpty() && SQLConstants.NonReserved.COUNT.equalsIgnoreCase(obj.getName())) { buffer.append(Tokens.ALL_COLS); } else { append(obj.getParameters()); } if (obj.getOrderBy() != null) { buffer.append(Tokens.SPACE); append(obj.getOrderBy()); } buffer.append(Tokens.RPAREN); if (obj.getCondition() != null) { buffer.append(Tokens.SPACE); buffer.append(FILTER); buffer.append(Tokens.LPAREN); buffer.append(WHERE); buffer.append(Tokens.SPACE); append(obj.getCondition()); buffer.append(Tokens.RPAREN); } } public void visit(Comparison obj) { if (obj.getLeftExpression() instanceof Comparison) { buffer.append(Tokens.LPAREN); append(obj.getLeftExpression()); buffer.append(Tokens.RPAREN); } else { append(obj.getLeftExpression()); } buffer.append(Tokens.SPACE); buffer.append(obj.getOperator()); buffer.append(Tokens.SPACE); if (obj.getRightExpression() instanceof Comparison) { buffer.append(Tokens.LPAREN); appendRightComparison(obj); buffer.append(Tokens.RPAREN); } else { appendRightComparison(obj); } } protected void appendRightComparison(Comparison obj) { append(obj.getRightExpression()); } public void visit(AndOr obj) { String opString = obj.getOperator().toString(); appendNestedCondition(obj, obj.getLeftCondition()); buffer.append(Tokens.SPACE) .append(opString) .append(Tokens.SPACE); appendNestedCondition(obj, obj.getRightCondition()); } protected void appendNestedCondition(AndOr parent, Condition condition) { if (condition instanceof AndOr) { AndOr nested = (AndOr)condition; if (nested.getOperator() != parent.getOperator()) { buffer.append(Tokens.LPAREN); append(condition); buffer.append(Tokens.RPAREN); return; } } append(condition); } public void visit(Delete obj) { buffer.append(DELETE) .append(Tokens.SPACE); appendSourceComment(obj); buffer.append(FROM) .append(Tokens.SPACE); append(obj.getTable()); if (obj.getWhere() != null) { buffer.append(Tokens.SPACE) .append(WHERE) .append(Tokens.SPACE); append(obj.getWhere()); } } private void appendSourceComment(Command obj) { if (appendedSourceComment) { return; } appendedSourceComment = true; buffer.append(getSourceComment(obj)); } /** * Take the specified derived group and element short names and determine a * replacement element name to use instead. Most commonly, this is used to strip * the group name if the group is a pseudo-group (DUAL) or the element is a pseudo-group * (ROWNUM). It may also be used to strip special information out of the name in source * value in some specialized cases. * * By default, this method returns null, indicating that the normal group and element * name logic should be used (group + "." + element). Subclasses should override and * implement this method if desired. * * @param group Group name, may be null * @param element Element name, never null * @return Replacement element name to be used as is (no modification will occur) * @since 5.0 */ protected String replaceElementName(String group, String element) { return null; } public void visit(ColumnReference obj) { buffer.append(getElementName(obj, !shortNameOnly)); } private String getElementName(ColumnReference obj, boolean qualify) { String groupName = null; NamedTable group = obj.getTable(); if (group != null && qualify) { if(group.getCorrelationName() != null) { groupName = group.getCorrelationName(); } else { AbstractMetadataRecord groupID = group.getMetadataObject(); if(groupID != null) { groupName = getName(groupID); } else { groupName = group.getName(); } } } String elemShortName = null; AbstractMetadataRecord elementID = obj.getMetadataObject(); if(elementID != null) { elemShortName = getName(elementID); } else { elemShortName = obj.getName(); } // Check whether a subclass wants to replace the element name to use in special circumstances String replacementElement = replaceElementName(groupName, elemShortName); if(replacementElement != null) { // If so, use it as is return replacementElement; } StringBuffer elementName = new StringBuffer(elemShortName.length()); // If not, do normal logic: [group + "."] + element if(groupName != null) { elementName.append(groupName); elementName.append(Tokens.DOT); } elementName.append(elemShortName); return elementName.toString(); } /** * @param elementName * @return * @since 4.3 */ public static String getShortName(String elementName) { int lastDot = elementName.lastIndexOf("."); //$NON-NLS-1$ if(lastDot >= 0) { elementName = elementName.substring(lastDot+1); } return elementName; } public void visit(Call obj) { appendCallStart(obj); if(obj.getMetadataObject() != null) { buffer.append(getName(obj.getMetadataObject())); } else { buffer.append(obj.getProcedureName()); } buffer.append(Tokens.LPAREN); final List<Argument> params = obj.getArguments(); if (params != null && params.size() != 0) { Argument param = null; for (int i = 0; i < params.size(); i++) { param = params.get(i); if (param.getDirection() == Direction.IN || param.getDirection() == Direction.INOUT) { if (i != 0) { buffer.append(Tokens.COMMA) .append(Tokens.SPACE); } append(param); } } } buffer.append(Tokens.RPAREN); } protected void appendCallStart(Call call) { buffer.append(EXEC) .append(Tokens.SPACE); } public void visit(Exists obj) { buffer.append(EXISTS) .append(Tokens.SPACE) .append(Tokens.LPAREN); append(obj.getSubquery()); buffer.append(Tokens.RPAREN); } protected boolean isInfixFunction(String function) { return infixFunctions.contains(function); } public void visit(Function obj) { String name = obj.getName(); List<Expression> args = obj.getParameters(); if(name.equalsIgnoreCase(CONVERT) || name.equalsIgnoreCase(CAST)) { Object typeValue = ((Literal)args.get(1)).getValue(); buffer.append(name); buffer.append(Tokens.LPAREN); append(args.get(0)); if(name.equalsIgnoreCase(CONVERT)) { buffer.append(Tokens.COMMA); buffer.append(Tokens.SPACE); } else { buffer.append(Tokens.SPACE); buffer.append(AS); buffer.append(Tokens.SPACE); } buffer.append(typeValue); buffer.append(Tokens.RPAREN); } else if(isInfixFunction(name)) { buffer.append(Tokens.LPAREN); if(args != null) { for(int i=0; i<args.size(); i++) { append(args.get(i)); if(i < (args.size()-1)) { buffer.append(Tokens.SPACE); buffer.append(name); buffer.append(Tokens.SPACE); } } } buffer.append(Tokens.RPAREN); } else if(name.equalsIgnoreCase(NonReserved.TIMESTAMPADD) || name.equalsIgnoreCase(NonReserved.TIMESTAMPDIFF)) { buffer.append(name); buffer.append(Tokens.LPAREN); if(args != null && args.size() > 0) { buffer.append(((Literal)args.get(0)).getValue()); for(int i=1; i<args.size(); i++) { buffer.append(Tokens.COMMA); buffer.append(Tokens.SPACE); append(args.get(i)); } } buffer.append(Tokens.RPAREN); } else if (name.equalsIgnoreCase(NonReserved.TRIM)) { buffer.append(name); buffer.append(Tokens.LPAREN); String value = (String)((Literal)args.get(0)).getValue(); if (!value.equalsIgnoreCase(BOTH)) { buffer.append(value); buffer.append(Tokens.SPACE); } append(args.get(1)); buffer.append(" "); //$NON-NLS-1$ buffer.append(FROM); buffer.append(" "); //$NON-NLS-1$ buffer.append(args.get(2)); buffer.append(")"); //$NON-NLS-1$ } else { buffer.append(obj.getName()) .append(Tokens.LPAREN); append(obj.getParameters()); buffer.append(Tokens.RPAREN); } } public void visit(NamedTable obj) { appendBaseName(obj); if (obj.getCorrelationName() != null) { buffer.append(Tokens.SPACE); if (useAsInGroupAlias()){ buffer.append(AS) .append(Tokens.SPACE); } buffer.append(obj.getCorrelationName()); } } protected void appendBaseName(NamedTable obj) { Table groupID = obj.getMetadataObject(); if(groupID != null) { buffer.append(getName(groupID)); } else { buffer.append(obj.getName()); } } /** * Indicates whether group alias should be of the form * "...FROM groupA AS X" or "...FROM groupA X". Certain * data sources (such as Oracle) may not support the first * form. * @return boolean */ protected boolean useAsInGroupAlias(){ return true; } public void visit(GroupBy obj) { buffer.append(GROUP) .append(Tokens.SPACE) .append(BY) .append(Tokens.SPACE); if (obj.isRollup()) { buffer.append(ROLLUP); buffer.append(Tokens.LPAREN); } append(obj.getElements()); if (obj.isRollup()) { buffer.append(Tokens.RPAREN); } } public void visit(In obj) { appendNested(obj.getLeftExpression()); if (obj.isNegated()) { buffer.append(Tokens.SPACE) .append(NOT); } buffer.append(Tokens.SPACE) .append(IN) .append(Tokens.SPACE) .append(Tokens.LPAREN); append(obj.getRightExpressions()); buffer.append(Tokens.RPAREN); } public void visit(DerivedTable obj) { if (obj.isLateral()) { appendLateralKeyword(); buffer.append(Tokens.SPACE); } buffer.append(Tokens.LPAREN); append(obj.getQuery()); buffer.append(Tokens.RPAREN); buffer.append(Tokens.SPACE); if(useAsInGroupAlias()) { buffer.append(AS); buffer.append(Tokens.SPACE); } buffer.append(obj.getCorrelationName()); } protected void appendLateralKeyword() { buffer.append(LATERAL); } public void visit(NamedProcedureCall obj) { if (obj.isLateral()) { appendLateralKeyword(); buffer.append(Tokens.SPACE); } buffer.append(Tokens.LPAREN); append(obj.getCall()); buffer.append(Tokens.RPAREN); buffer.append(Tokens.SPACE); if(useAsInGroupAlias()) { buffer.append(AS); buffer.append(Tokens.SPACE); } buffer.append(obj.getCorrelationName()); } public void visit(Insert obj) { if (obj.isUpsert()) { buffer.append(getUpsertKeyword()).append(Tokens.SPACE); } else { buffer.append(getInsertKeyword()).append(Tokens.SPACE); } appendSourceComment(obj); buffer.append(INTO).append(Tokens.SPACE); append(obj.getTable()); buffer.append(Tokens.SPACE).append(Tokens.LPAREN); this.shortNameOnly = true; append(obj.getColumns()); this.shortNameOnly = false; buffer.append(Tokens.RPAREN); buffer.append(Tokens.SPACE); append(obj.getValueSource()); } protected String getInsertKeyword() { return INSERT; } protected String getUpsertKeyword() { return NonReserved.UPSERT; } @Override public void visit(ExpressionValueSource obj) { buffer.append(VALUES).append(Tokens.SPACE).append(Tokens.LPAREN); append(obj.getValues()); buffer.append(Tokens.RPAREN); } @Override public void visit(Parameter obj) { buffer.append('?'); } public void visit(IsNull obj) { appendNested(obj.getExpression()); buffer.append(Tokens.SPACE) .append(IS) .append(Tokens.SPACE); if (obj.isNegated()) { buffer.append(NOT) .append(Tokens.SPACE); } buffer.append(NULL); } /** * Condition operators have lower precedence than LIKE/SIMILAR/IS * @param ex */ private void appendNested(Expression ex) { boolean useParens = ex instanceof Condition; if (useParens) { buffer.append(Tokens.LPAREN); } append(ex); if (useParens) { buffer.append(Tokens.RPAREN); } } public void visit(Join obj) { TableReference leftItem = obj.getLeftItem(); if(useParensForLHSJoins() && leftItem instanceof Join) { buffer.append(Tokens.LPAREN); append(leftItem); buffer.append(Tokens.RPAREN); } else { append(leftItem); } buffer.append(Tokens.SPACE); switch(obj.getJoinType()) { case CROSS_JOIN: buffer.append(CROSS); break; case FULL_OUTER_JOIN: buffer.append(FULL) .append(Tokens.SPACE) .append(OUTER); break; case INNER_JOIN: buffer.append(INNER); break; case LEFT_OUTER_JOIN: buffer.append(LEFT) .append(Tokens.SPACE) .append(OUTER); break; case RIGHT_OUTER_JOIN: buffer.append(RIGHT) .append(Tokens.SPACE) .append(OUTER); break; default: buffer.append(UNDEFINED); } buffer.append(Tokens.SPACE) .append(JOIN) .append(Tokens.SPACE); TableReference rightItem = obj.getRightItem(); if(rightItem instanceof Join && (useParensForJoins() || obj.getJoinType() == Join.JoinType.CROSS_JOIN)) { buffer.append(Tokens.LPAREN); append(rightItem); buffer.append(Tokens.RPAREN); } else { append(rightItem); } final Condition condition = obj.getCondition(); if (condition != null) { buffer.append(Tokens.SPACE) .append(ON) .append(Tokens.SPACE); append(condition); } } /** * If a nested left hand join should have parens * @return */ protected boolean useParensForLHSJoins() { return useParensForJoins(); } public void visit(Like obj) { append(obj.getLeftExpression()); if (obj.isNegated()) { buffer.append(Tokens.SPACE) .append(NOT); } buffer.append(Tokens.SPACE); switch (obj.getMode()) { case LIKE: buffer.append(LIKE); break; case SIMILAR: buffer.append(SIMILAR) .append(Tokens.SPACE) .append(TO); case REGEX: buffer.append(getLikeRegexString()); } buffer.append(Tokens.SPACE); append(obj.getRightExpression()); if (obj.getEscapeCharacter() != null) { buffer.append(Tokens.SPACE) .append(ESCAPE) .append(Tokens.SPACE) .append(Tokens.QUOTE) .append(escapeString(String.valueOf(obj.getEscapeCharacter()), Tokens.QUOTE)) .append(Tokens.QUOTE); } } protected String getLikeRegexString() { return LIKE_REGEX; } public void visit(Limit obj) { buffer.append(LIMIT) .append(Tokens.SPACE); if (obj.getRowOffset() > 0) { buffer.append(obj.getRowOffset()) .append(Tokens.COMMA) .append(Tokens.SPACE); } buffer.append(obj.getRowLimit()); } public void visit(Literal obj) { if (obj.getValue() == null) { buffer.append(NULL); } else { Class<?> type = obj.getType(); appendLiteral(obj, type); } } protected void appendLiteral(Literal obj, Class<?> type) { String val = obj.getValue().toString(); if(Number.class.isAssignableFrom(type)) { buffer.append(val); } else if(type.equals(DataTypeManager.DefaultDataClasses.BOOLEAN)) { buffer.append(obj.getValue().equals(Boolean.TRUE) ? TRUE : FALSE); } else if(type.equals(DataTypeManager.DefaultDataClasses.TIMESTAMP)) { buffer.append("{ts '") //$NON-NLS-1$ .append(val) .append("'}"); //$NON-NLS-1$ } else if(type.equals(DataTypeManager.DefaultDataClasses.TIME)) { buffer.append("{t '") //$NON-NLS-1$ .append(val) .append("'}"); //$NON-NLS-1$ } else if(type.equals(DataTypeManager.DefaultDataClasses.DATE)) { buffer.append("{d '") //$NON-NLS-1$ .append(val) .append("'}"); //$NON-NLS-1$ } else if (type.equals(DataTypeManager.DefaultDataClasses.VARBINARY)) { buffer.append("X'") //$NON-NLS-1$ .append(val) .append("'"); //$NON-NLS-1$ } else { buffer.append(Tokens.QUOTE) .append(escapeString(val, Tokens.QUOTE)) .append(Tokens.QUOTE); } } public void visit(Not obj) { buffer.append(NOT) .append(Tokens.SPACE) .append(Tokens.LPAREN); append(obj.getCriteria()); buffer.append(Tokens.RPAREN); } public void visit(OrderBy obj) { buffer.append(ORDER) .append(Tokens.SPACE) .append(BY) .append(Tokens.SPACE); append(obj.getSortSpecifications()); } public void visit(SortSpecification obj) { append(obj.getExpression()); if (obj.getOrdering() == Ordering.DESC) { buffer.append(Tokens.SPACE) .append(DESC); } // Don't print default "ASC" if (obj.getNullOrdering() != null) { buffer.append(Tokens.SPACE) .append(NonReserved.NULLS) .append(Tokens.SPACE) .append(obj.getNullOrdering().name()); } } public void visit(Argument obj) { visitNode(obj.getExpression()); } public void visit(Select obj) { if (obj.getWith() != null) { append(obj.getWith()); } buffer.append(SELECT).append(Tokens.SPACE); appendSourceComment(obj); if (obj.isDistinct()) { buffer.append(DISTINCT).append(Tokens.SPACE); } if (useSelectLimit() && obj.getLimit() != null) { append(obj.getLimit()); buffer.append(Tokens.SPACE); } append(obj.getDerivedColumns()); if (obj.getFrom() != null && !obj.getFrom().isEmpty()) { buffer.append(Tokens.SPACE).append(FROM).append(Tokens.SPACE); append(obj.getFrom()); } if (obj.getWhere() != null) { buffer.append(Tokens.SPACE) .append(WHERE) .append(Tokens.SPACE); append(obj.getWhere()); } if (obj.getGroupBy() != null) { buffer.append(Tokens.SPACE); append(obj.getGroupBy()); } if (obj.getHaving() != null) { buffer.append(Tokens.SPACE) .append(HAVING) .append(Tokens.SPACE); append(obj.getHaving()); } if (obj.getOrderBy() != null) { buffer.append(Tokens.SPACE); append(obj.getOrderBy()); } if (!useSelectLimit() && obj.getLimit() != null) { buffer.append(Tokens.SPACE); append(obj.getLimit()); } } public void visit(SearchedCase obj) { buffer.append(CASE); for (SearchedWhenClause swc : obj.getCases()) { append(swc); } if (obj.getElseExpression() != null) { buffer.append(Tokens.SPACE) .append(ELSE) .append(Tokens.SPACE); append(obj.getElseExpression()); } buffer.append(Tokens.SPACE) .append(END); } @Override public void visit(SearchedWhenClause obj) { buffer.append(Tokens.SPACE).append(WHEN) .append(Tokens.SPACE); append(obj.getCondition()); buffer.append(Tokens.SPACE).append(THEN) .append(Tokens.SPACE); append(obj.getResult()); } protected String getSourceComment(Command command) { return ""; //$NON-NLS-1$ } public void visit(ScalarSubquery obj) { buffer.append(Tokens.LPAREN); append(obj.getSubquery()); buffer.append(Tokens.RPAREN); } public void visit(DerivedColumn obj) { append(obj.getExpression()); if (obj.getAlias() != null) { buffer.append(Tokens.SPACE) .append(AS) .append(Tokens.SPACE) .append(obj.getAlias()); } } public void visit(SubqueryComparison obj) { append(obj.getLeftExpression()); buffer.append(Tokens.SPACE); switch(obj.getOperator()) { case EQ: buffer.append(Tokens.EQ); break; case GE: buffer.append(Tokens.GE); break; case GT: buffer.append(Tokens.GT); break; case LE: buffer.append(Tokens.LE); break; case LT: buffer.append(Tokens.LT); break; case NE: buffer.append(Tokens.NE); break; default: buffer.append(UNDEFINED); } buffer.append(Tokens.SPACE); switch(obj.getQuantifier()) { case ALL: buffer.append(ALL); break; case SOME: buffer.append(SOME); break; default: buffer.append(UNDEFINED); } buffer.append(Tokens.SPACE); buffer.append(Tokens.LPAREN); append(obj.getSubquery()); buffer.append(Tokens.RPAREN); } public void visit(SubqueryIn obj) { append(obj.getLeftExpression()); if (obj.isNegated()) { buffer.append(Tokens.SPACE) .append(NOT); } buffer.append(Tokens.SPACE) .append(IN) .append(Tokens.SPACE) .append(Tokens.LPAREN); append(obj.getSubquery()); buffer.append(Tokens.RPAREN); } public void visit(Update obj) { buffer.append(UPDATE) .append(Tokens.SPACE); appendSourceComment(obj); append(obj.getTable()); buffer.append(Tokens.SPACE) .append(SET) .append(Tokens.SPACE); append(obj.getChanges()); if (obj.getWhere() != null) { buffer.append(Tokens.SPACE) .append(WHERE) .append(Tokens.SPACE); append(obj.getWhere()); } } public void visit(SetClause clause) { shortNameOnly = true; append(clause.getSymbol()); shortNameOnly = false; buffer.append(Tokens.SPACE).append(Tokens.EQ).append(Tokens.SPACE); append(clause.getValue()); } public void visit(SetQuery obj) { if (obj.getWith() != null) { append(obj.getWith()); } appendSetQuery(obj, obj.getLeftQuery(), false); buffer.append(Tokens.SPACE); appendSetOperation(obj.getOperation()); if(obj.isAll()) { buffer.append(Tokens.SPACE); buffer.append(ALL); } buffer.append(Tokens.SPACE); appendSetQuery(obj, obj.getRightQuery(), true); OrderBy orderBy = obj.getOrderBy(); if(orderBy != null) { buffer.append(Tokens.SPACE); append(orderBy); } Limit limit = obj.getLimit(); if(limit != null) { buffer.append(Tokens.SPACE); append(limit); } } protected void appendSetOperation(SetQuery.Operation operation) { buffer.append(operation); } protected boolean useParensForSetQueries() { return false; } protected void appendSetQuery(SetQuery parent, QueryExpression obj, boolean right) { if(shouldNestSetChild(parent, obj, right)) { buffer.append(Tokens.LPAREN); append(obj); buffer.append(Tokens.RPAREN); } else { if (!parent.isAll() && obj instanceof SetQuery) { ((SetQuery)obj).setAll(false); } append(obj); } } protected boolean shouldNestSetChild(SetQuery parent, QueryExpression 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)); } @Override public void visit(With obj) { appendedSourceComment = true; appendWithKeyword(obj); buffer.append(Tokens.SPACE); append(obj.getItems()); buffer.append(Tokens.SPACE); appendedSourceComment = false; } protected void appendWithKeyword(With obj) { buffer.append(WITH); } @Override public void visit(WithItem obj) { append(obj.getTable()); buffer.append(Tokens.SPACE); if (obj.getColumns() != null) { buffer.append(Tokens.LPAREN); shortNameOnly = true; append(obj.getColumns()); shortNameOnly = false; buffer.append(Tokens.RPAREN); buffer.append(Tokens.SPACE); } buffer.append(AS); buffer.append(Tokens.SPACE); buffer.append(Tokens.LPAREN); if (obj.getSubquery() == null) { buffer.append(UNDEFINED_PARAM); } else { append(obj.getSubquery()); } buffer.append(Tokens.RPAREN); } @Override public void visit(WindowFunction windowFunction) { append(windowFunction.getFunction()); buffer.append(Tokens.SPACE); buffer.append(OVER); buffer.append(Tokens.SPACE); append(windowFunction.getWindowSpecification()); } @Override public void visit(WindowSpecification windowSpecification) { buffer.append(Tokens.LPAREN); boolean needsSpace = false; if (windowSpecification.getPartition() != null) { buffer.append(PARTITION); buffer.append(Tokens.SPACE); buffer.append(BY); buffer.append(Tokens.SPACE); append(windowSpecification.getPartition()); needsSpace = true; } if (windowSpecification.getOrderBy() != null) { if (needsSpace) { buffer.append(Tokens.SPACE); } append(windowSpecification.getOrderBy()); } buffer.append(Tokens.RPAREN); } @Override public void visit(Array array) { buffer.append(Tokens.LPAREN); append(array.getExpressions()); if (array.getExpressions().size() == 1) { buffer.append(Tokens.COMMA); } buffer.append(Tokens.RPAREN); } /** * Gets the SQL string representation for a given LanguageObject. * @param obj the root of the LanguageObject hierarchy that needs to be * converted. This can be any subtree, and does not need to be a top-level * command * @return the SQL representation of that LanguageObject hierarchy */ public static String getSQLString(LanguageObject obj) { SQLStringVisitor visitor = new SQLStringVisitor(); visitor.append(obj); return visitor.toString(); } protected boolean useParensForJoins() { return false; } protected boolean useSelectLimit() { return false; } public interface Substitutor { void substitute(Argument arg, StringBuilder builder, int index); } public static void parseNativeQueryParts(String nativeQuery, List<Argument> list, StringBuilder stringBuilder, Substitutor substitutor) { Matcher m = pattern.matcher(nativeQuery); for (int i = 0; i < nativeQuery.length();) { if (!m.find(i)) { stringBuilder.append(nativeQuery.substring(i)); break; } if (m.start() != i) { stringBuilder.append(nativeQuery.substring(i, m.start())); } String match = m.group(); int end = match.lastIndexOf('$'); if ((end&0x1) == 1) { //escaped stringBuilder.append(match.substring((end+1)/2)); } else { if (end != 0) { stringBuilder.append(match.substring(0, end/2)); } int index = Integer.parseInt(match.substring(end + 1))-1; if (index < 0 || index >= list.size()) { throw new IllegalArgumentException(DataPlugin.Util.getString("SQLConversionVisitor.invalid_parameter", index+1, list.size())); //$NON-NLS-1$ } Argument arg = list.get(index); if (arg.getDirection() != Direction.IN) { throw new IllegalArgumentException(DataPlugin.Util.getString("SQLConversionVisitor.not_in_parameter", index+1)); //$NON-NLS-1$ } substitutor.substitute(arg, stringBuilder, index); } i = m.end(); } } }