/* * 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 java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import org.teiid.language.Expression; import org.teiid.language.Function; import org.teiid.language.LanguageFactory; import org.teiid.language.Literal; import org.teiid.translator.SourceSystemFunctions; import org.teiid.translator.TypeFacility; /** * Base class for handling the convert function. * <p>Convert is by far the most complicated pushdown function since it actually * represents a matrix of possible functions. Additionally not every source supports * the same semantics as our conversions.</p> * <p>Each instance of this class makes a best effort at handling converts for for a * given source - compensating for differing string representation, the lack a time type * etc. * <p>The choice of conversion logic is as follows: * <ul> * <li>Provide specific conversion between the source and target - {@link #addConvert(int, int, FunctionModifier)} * mostly one do not need to provide any conversion if the default is cast(srcType AS targetType), however if the source * database requires different specific format for example to cast(srctype, targetType FORMAT 'more-info') this conversion needs to be added</li> * <li>Filter common implicit conversions</li> * <li>Look for a general source conversion - {@link #addSourceConversion(FunctionModifier, int...)}</li> * <li>Look for a general target conversion - {@link #addTypeConversion(FunctionModifier, int...)}. If the source * database provides a specific function for converting *any* source datatype to this target datatype then use this to define it. * Like in oracle "to_char" function will convert any other data type to string. Use this to define those kind of conversions * convert any data type to string. so you can use this for purpose.</li> * <li>Type maps from database data type to Teiid runtime types. - {@link #addTypeMapping(String, int...)} * define mappings for every datatype available in database. The cast operation will replace the target type (teiid type) with the given * native type in the cast operation generated. Do not need to really look at implicit/explicit conversions that are * supported by the source database, because when a cast is defined on the sql it is up to the source database to apply it * as implicit or explicit operation. Teiid generates cast always when needed</li> * <li>Drop the conversion</li> * </ul> */ public class ConvertModifier extends FunctionModifier { public static class FormatModifier extends AliasModifier { private String format; public FormatModifier(String alias) { super(alias); } public FormatModifier(String alias, String format) { super(alias); this.format = format; } @Override public List<?> translate(Function function) { modify(function); if (format == null) { function.getParameters().remove(1); } else { ((Literal)function.getParameters().get(1)).setValue(format); } return null; } } private Map<Integer, String> typeMapping = new HashMap<Integer, String>(); private Map<Integer, FunctionModifier> typeModifier = new HashMap<Integer, FunctionModifier>(); private Map<Integer, FunctionModifier> sourceModifier = new HashMap<Integer, FunctionModifier>(); private Map<List<Integer>, FunctionModifier> specificConverts = new HashMap<List<Integer>, FunctionModifier>(); private boolean booleanNumeric; private boolean wideningNumericImplicit; private boolean booleanNullable = true; public void addTypeConversion(FunctionModifier convert, int ... targetType) { for (int i : targetType) { this.typeModifier.put(i, convert); } } public void addSourceConversion(FunctionModifier convert, int ... sourceType) { for (int i : sourceType) { this.sourceModifier.put(i, convert); } } public void addTypeMapping(String nativeType, int ... targetType) { for (int i : targetType) { typeMapping.put(i, nativeType); } } public void setWideningNumericImplicit(boolean wideningNumericImplicit) { this.wideningNumericImplicit = wideningNumericImplicit; } public void addConvert(int sourceType, int targetType, FunctionModifier convert) { specificConverts.put(Arrays.asList(sourceType, targetType), convert); } @Override public List<?> translate(Function function) { function.setName("cast"); //$NON-NLS-1$ int targetCode = getCode(function.getType()); List<Expression> args = function.getParameters(); Class<?> srcType = args.get(0).getType(); int sourceCode = getCode(srcType); List<Integer> convesionCode = Arrays.asList(sourceCode, targetCode); FunctionModifier convert = specificConverts.get(convesionCode); if (convert != null) { return convert.translate(function); } boolean implicit = sourceCode == CHAR && targetCode == STRING; if (targetCode >= BYTE && targetCode <= BIGDECIMAL) { if (booleanNumeric && sourceCode == BOOLEAN) { sourceCode = BYTE; implicit = targetCode == BYTE; } implicit |= wideningNumericImplicit && sourceCode >= BYTE && sourceCode <= BIGDECIMAL && sourceCode < targetCode; } if (!implicit) { convert = this.sourceModifier.get(sourceCode); if (convert != null && (!convert.equals(sourceModifier.get(targetCode)) || sourceCode == targetCode)) { //checks for implicit, but allows for dummy converts return convert.translate(function); } convert = this.typeModifier.get(targetCode); if (convert != null && (!convert.equals(typeModifier.get(sourceCode)) || sourceCode == targetCode)) { //checks for implicit, but allows for dummy converts return convert.translate(function); } String type = typeMapping.get(targetCode); if (type != null && (!type.equals(typeMapping.get(sourceCode)) || sourceCode == targetCode)) { //checks for implicit, but allows for dummy converts ((Literal)function.getParameters().get(1)).setValue(type); return null; } } return Arrays.asList(function.getParameters().get(0)); } /** * IMPORTANT: only for use with default runtime type names * @param langFactory * @param expr * @param typeName * @return */ public static Function createConvertFunction(LanguageFactory langFactory, Expression expr, String typeName) { Class<?> type = TypeFacility.getDataTypeClass(typeName); return langFactory.createFunction(SourceSystemFunctions.CONVERT, new Expression[] {expr, langFactory.createLiteral(typeName, type)}, type); } public void addNumericBooleanConversions() { this.booleanNumeric = true; //number -> boolean this.addTypeConversion(new FunctionModifier() { @Override public List<?> translate(Function function) { Expression stringValue = function.getParameters().get(0); return Arrays.asList("CASE WHEN ", stringValue, " = 0 THEN 0 WHEN ", stringValue, " IS NOT NULL THEN 1 END"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } }, FunctionModifier.BOOLEAN); this.addConvert(FunctionModifier.BOOLEAN, FunctionModifier.STRING, new FunctionModifier() { @Override public List<?> translate(Function function) { Expression booleanValue = function.getParameters().get(0); if (!booleanNullable) { return Arrays.asList("CASE WHEN ", booleanValue, " = 0 THEN 'false' ELSE 'true' END"); //$NON-NLS-1$ //$NON-NLS-2$ } return Arrays.asList("CASE WHEN ", booleanValue, " = 0 THEN 'false' WHEN ", booleanValue, " IS NOT NULL THEN 'true' END"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } }); this.addConvert(FunctionModifier.STRING, FunctionModifier.BOOLEAN, new FunctionModifier() { @Override public List<?> translate(Function function) { Expression stringValue = function.getParameters().get(0); return Arrays.asList("CASE WHEN ", stringValue, " IN ('false', '0') THEN 0 WHEN ", stringValue, " IS NOT NULL THEN 1 END"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } }); } public void setBooleanNullable(boolean booleanNullable) { this.booleanNullable = booleanNullable; } public boolean hasTypeMapping(int type) { return this.typeMapping.containsKey(type); } }