/* * 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.translator.jdbc; import static org.teiid.language.SQLConstants.Reserved.*; import java.math.BigDecimal; import java.sql.Time; import java.sql.Timestamp; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.util.ArrayList; import java.util.Collections; import java.util.IdentityHashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import org.teiid.core.types.BinaryType; import org.teiid.core.types.DataTypeManager; import org.teiid.language.*; import org.teiid.language.Argument.Direction; import org.teiid.language.SQLConstants.Reserved; import org.teiid.language.SQLConstants.Tokens; import org.teiid.language.SetQuery.Operation; import org.teiid.language.visitor.SQLStringVisitor; import org.teiid.metadata.AbstractMetadataRecord; import org.teiid.metadata.FunctionMethod; import org.teiid.metadata.Procedure; import org.teiid.translator.ExecutionContext; import org.teiid.translator.TypeFacility; /** * This visitor takes an ICommand and does DBMS-specific conversion on it * to produce a SQL String. This class is expected to be subclassed. */ public class SQLConversionVisitor extends SQLStringVisitor implements SQLStringVisitor.Substitutor { public static final String TEIID_NON_PREPARED = AbstractMetadataRecord.RELATIONAL_URI + "non-prepared"; //$NON-NLS-1$ private static DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#############################0.0#############################", DecimalFormatSymbols.getInstance(Locale.US)); //$NON-NLS-1$ private static double SCIENTIFIC_LOW = Math.pow(10, -3); private static double SCIENTIFIC_HIGH = Math.pow(10, 7); private ExecutionContext context; private JDBCExecutionFactory executionFactory; private boolean prepared; private boolean usingBinding; private List preparedValues = new ArrayList(); private Set<LanguageObject> recursionObjects = Collections.newSetFromMap(new IdentityHashMap<LanguageObject, Boolean>()); private Map<LanguageObject, Object> translations = new IdentityHashMap<LanguageObject, Object>(); private boolean replaceWithBinding = false; public SQLConversionVisitor(JDBCExecutionFactory ef) { this.executionFactory = ef; this.prepared = executionFactory.usePreparedStatements(); } @Override public void append(LanguageObject obj) { if (shortNameOnly && obj instanceof ColumnReference) { super.append(obj); return; } boolean replacementMode = replaceWithBinding; if (obj instanceof Command || obj instanceof Function) { /* * In general it is not appropriate to use bind values within a function * unless the particulars of the function parameters are know. * As needed, other visitors or modifiers can set the literals used within * a particular function as bind variables. */ this.replaceWithBinding = false; } List<?> parts = null; if (!recursionObjects.contains(obj)) { Object trans = this.translations.get(obj); if (trans instanceof List<?>) { parts = (List<?>)trans; } else if (trans instanceof LanguageObject) { obj = (LanguageObject)trans; } else { parts = executionFactory.translate(obj, context); if (parts != null) { this.translations.put(obj, parts); } else { this.translations.put(obj, obj); } } } if (parts != null) { recursionObjects.add(obj); for (Object part : parts) { if(part instanceof LanguageObject) { append((LanguageObject)part); } else { buffer.append(part); } } recursionObjects.remove(obj); } else { super.append(obj); } this.replaceWithBinding = replacementMode; } /** * @param type * @param object * @param valuesbuffer */ protected void translateSQLType(Class<?> type, Object obj, StringBuilder valuesbuffer) { if (obj == null) { valuesbuffer.append(Reserved.NULL); } else { if(Number.class.isAssignableFrom(type)) { boolean useFormatting = false; if (!executionFactory.useScientificNotation()) { if (Double.class.isAssignableFrom(type)){ double value = Math.abs(((Double)obj).doubleValue()); useFormatting = (value <= SCIENTIFIC_LOW || value >= SCIENTIFIC_HIGH); } else if (Float.class.isAssignableFrom(type)){ float value = Math.abs(((Float)obj).floatValue()); useFormatting = (value <= SCIENTIFIC_LOW || value >= SCIENTIFIC_HIGH); } else if (BigDecimal.class.isAssignableFrom(type)) { valuesbuffer.append(((BigDecimal)obj).toPlainString()); return; } } // The formatting is to avoid the so-called "scientic-notation" // where toString will use for numbers greater than 10p7 and // less than 10p-3, where database may not understand. if (useFormatting) { synchronized (DECIMAL_FORMAT) { valuesbuffer.append(DECIMAL_FORMAT.format(obj)); } } else { valuesbuffer.append(obj); } } else if(type.equals(TypeFacility.RUNTIME_TYPES.BOOLEAN)) { valuesbuffer.append(executionFactory.translateLiteralBoolean((Boolean)obj)); } else if(type.equals(TypeFacility.RUNTIME_TYPES.TIMESTAMP)) { valuesbuffer.append(executionFactory.translateLiteralTimestamp((Timestamp)obj)); } else if(type.equals(TypeFacility.RUNTIME_TYPES.TIME)) { if (!executionFactory.hasTimeType()) { valuesbuffer.append(executionFactory.translateLiteralTimestamp(new Timestamp(((Time)obj).getTime()))); } else { valuesbuffer.append(executionFactory.translateLiteralTime((Time)obj)); } } else if(type.equals(TypeFacility.RUNTIME_TYPES.DATE)) { valuesbuffer.append(executionFactory.translateLiteralDate((java.sql.Date)obj)); } else if (type.equals(DataTypeManager.DefaultDataClasses.VARBINARY)) { valuesbuffer.append(executionFactory.translateLiteralBinaryType((BinaryType)obj)); } else { // If obj is string, toSting() will not create a new String // object, it returns it self, so new object creation. String val = obj.toString(); if (useUnicodePrefix()) { for (int i = 0; i < val.length(); i++) { if (val.charAt(i) > 127) { buffer.append("N"); //$NON-NLS-1$ break; } } } valuesbuffer.append(Tokens.QUOTE) .append(escapeString(val, Tokens.QUOTE)) .append(Tokens.QUOTE); } } } protected boolean useUnicodePrefix() { return this.executionFactory.useUnicodePrefix(); } /** * @see org.teiid.language.visitor.SQLStringVisitor#visit(org.teiid.language.Call) */ public void visit(Call obj) { usingBinding = true; Procedure p = obj.getMetadataObject(); if (p != null) { String nativeQuery = p.getProperty(TEIID_NATIVE_QUERY, false); if (nativeQuery != null) { this.prepared = !Boolean.valueOf(p.getProperty(TEIID_NON_PREPARED, false)); if (this.prepared) { this.preparedValues = new ArrayList<Object>(); } parseNativeQueryParts(nativeQuery, obj.getArguments(), buffer, this); return; } } if (obj.isTableReference()) { usingBinding = false; super.visit(obj); return; } this.prepared = true; /* * preparedValues is now a list of procedure params instead of just values */ this.preparedValues = obj.getArguments(); buffer.append(generateSqlForStoredProcedure(obj)); } public void visit(Function obj) { FunctionMethod f = obj.getMetadataObject(); if (f != null) { String nativeQuery = f.getProperty(TEIID_NATIVE_QUERY, false); if (nativeQuery != null) { List<Argument> args = new ArrayList<Argument>(obj.getParameters().size()); for (Expression p : obj.getParameters()) { args.add(new Argument(Direction.IN, p, p.getType(), null)); } parseNativeQueryParts(nativeQuery, args, buffer, this); return; } } super.visit(obj); } @Override public void visit(Parameter obj) { buffer.append(UNDEFINED_PARAM); preparedValues.add(obj); usingBinding = true; } /** * @see org.teiid.language.visitor.SQLStringVisitor#visit(org.teiid.language.Literal) */ public void visit(Literal obj) { if (this.prepared && ((replaceWithBinding && obj.isBindEligible()) || TranslatedCommand.isBindEligible(obj))) { buffer.append(UNDEFINED_PARAM); preparedValues.add(obj); usingBinding = true; } else { translateSQLType(obj.getType(), obj.getValue(), buffer); } } @Override public void visit(In obj) { replaceWithBinding = true; super.visit(obj); } @Override public void visit(Like obj) { replaceWithBinding = true; super.visit(obj); } @Override public void visit(Comparison obj) { replaceWithBinding = true; super.visit(obj); } @Override public void visit(ExpressionValueSource obj) { replaceWithBinding = true; super.visit(obj); } @Override public void visit(SetClause obj) { replaceWithBinding = true; super.visit(obj); } @Override public void visit(DerivedColumn obj) { replaceWithBinding = false; super.visit(obj); } @Override public void visit(SearchedCase obj) { replaceWithBinding = false; super.visit(obj); } /** * Set the per-command execution context on this visitor. * @param context ExecutionContext * @since 4.3 */ public void setExecutionContext(ExecutionContext context) { this.context = context; } /** * Retrieve the per-command execution context for this visitor * (intended for subclasses to use). * @return * @since 4.3 */ protected ExecutionContext getExecutionContext() { return this.context; } protected String getSourceComment(Command command) { return this.executionFactory.getSourceComment(this.context, command); } /** * This is a generic implementation. Subclass should override this method * if necessary. * @param exec The command for the stored procedure. * @return String to be executed by CallableStatement. */ protected String generateSqlForStoredProcedure(Call exec) { StringBuffer prepareCallBuffer = new StringBuffer(); prepareCallBuffer.append(getSourceComment(exec)); prepareCallBuffer.append("{"); //$NON-NLS-1$ List<Argument> params = exec.getArguments(); //check whether a "?" is needed if there are returns boolean needQuestionMark = exec.getReturnType() != null; if(needQuestionMark){ prepareCallBuffer.append("?= "); //$NON-NLS-1$ } prepareCallBuffer.append("call ");//$NON-NLS-1$ prepareCallBuffer.append(exec.getMetadataObject() != null ? getName(exec.getMetadataObject()) : exec.getProcedureName()); prepareCallBuffer.append("("); //$NON-NLS-1$ int numberOfParameters = 0; for (Argument param : params) { if(param.getDirection() == Direction.IN || param.getDirection() == Direction.OUT || param.getDirection() == Direction.INOUT){ if(numberOfParameters > 0){ prepareCallBuffer.append(","); //$NON-NLS-1$ } prepareCallBuffer.append("?"); //$NON-NLS-1$ numberOfParameters++; } } prepareCallBuffer.append(")"); //$NON-NLS-1$ prepareCallBuffer.append("}"); //$NON-NLS-1$ return prepareCallBuffer.toString(); } /** * @return the preparedValues */ List getPreparedValues() { return this.preparedValues; } public boolean isPrepared() { return prepared; } public void setPrepared(boolean prepared) { this.prepared = prepared; } public boolean isUsingBinding() { return usingBinding; } @Override protected boolean useAsInGroupAlias() { return this.executionFactory.useAsInGroupAlias(); } @Override protected boolean useParensForSetQueries() { return executionFactory.useParensForSetQueries(); } @Override protected String replaceElementName(String group, String element) { return executionFactory.replaceElementName(group, element); } @Override protected void appendSetOperation(Operation operation) { buffer.append(executionFactory.getSetOperationString(operation)); } @Override protected boolean useParensForJoins() { return executionFactory.useParensForJoins(); } protected boolean useSelectLimit() { return executionFactory.useSelectLimit(); } @Override protected String getLikeRegexString() { return executionFactory.getLikeRegexString(); } @Override protected void appendBaseName(NamedTable obj) { if (obj.getMetadataObject() != null) { String nativeQuery = obj.getMetadataObject().getProperty(TEIID_NATIVE_QUERY, false); if (nativeQuery != null) { if (obj.getCorrelationName() == null) { throw new IllegalArgumentException(JDBCPlugin.Util.gs(JDBCPlugin.Event.TEIID11020, obj.getName())); } buffer.append(Tokens.LPAREN).append(nativeQuery).append(Tokens.RPAREN); if (obj.getCorrelationName() == null) { buffer.append(Tokens.SPACE); if (useAsInGroupAlias()){ buffer.append(AS).append(Tokens.SPACE); } } return; } } super.appendBaseName(obj); } @Override public void substitute(Argument arg, StringBuilder builder, int index) { if (this.prepared && arg.getExpression() instanceof Literal) { buffer.append('?'); this.preparedValues.add(arg); } else { visit(arg); } } @Override public void visit(GroupBy obj) { if (obj.isRollup() && executionFactory.useWithRollup()) { obj.setRollup(false); super.visit(obj); obj.setRollup(true); buffer.append(" WITH ROLLUP"); //$NON-NLS-1$ return; } super.visit(obj); } @Override protected void appendLateralKeyword() { buffer.append(this.executionFactory.getLateralKeyword()); } }