/*
* 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.teradata;
import static org.teiid.translator.TypeFacility.RUNTIME_NAMES.*;
import java.sql.Date;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.teiid.language.*;
import org.teiid.translator.ExecutionContext;
import org.teiid.translator.SourceSystemFunctions;
import org.teiid.translator.Translator;
import org.teiid.translator.TranslatorException;
import org.teiid.translator.TypeFacility;
import org.teiid.translator.jdbc.AliasModifier;
import org.teiid.translator.jdbc.ConvertModifier;
import org.teiid.translator.jdbc.FunctionModifier;
import org.teiid.translator.jdbc.JDBCExecutionFactory;
import org.teiid.translator.jdbc.SQLConversionVisitor;
/**
* Teradata database Release 12
*/
@Translator(name="teradata", description="A translator for Teradata Database")
public class TeradataExecutionFactory extends JDBCExecutionFactory {
public static String TERADATA = "teradata"; //$NON-NLS-1$
protected ConvertModifier convert = new ConvertModifier();
public TeradataExecutionFactory() {
setMaxDependentInPredicates(5);
//teradata documentation does not make it clear that there is a hard limit. this value comes from hibernate
setMaxInCriteriaSize(1024);
}
@Override
public void start() throws TranslatorException {
super.start();
convert.addTypeMapping("byteint", FunctionModifier.BYTE, FunctionModifier.SHORT, FunctionModifier.BOOLEAN); //$NON-NLS-1$
convert.addTypeMapping("double precision", FunctionModifier.DOUBLE); //$NON-NLS-1$
convert.addTypeMapping("numeric(18,0)", FunctionModifier.BIGINTEGER); //$NON-NLS-1$
convert.addTypeMapping("char(1)", FunctionModifier.CHAR); //$NON-NLS-1$
convert.addConvert(FunctionModifier.TIMESTAMP, FunctionModifier.TIME, new CastModifier("TIME")); //$NON-NLS-1$
convert.addConvert(FunctionModifier.TIMESTAMP, FunctionModifier.DATE, new CastModifier("DATE")); //$NON-NLS-1$
convert.addConvert(FunctionModifier.TIME, FunctionModifier.TIMESTAMP, new CastModifier("TIMESTAMP")); //$NON-NLS-1$
convert.addConvert(FunctionModifier.DATE, FunctionModifier.TIMESTAMP, new CastModifier("TIMESTAMP")); //$NON-NLS-1$
convert.addConvert(FunctionModifier.STRING, FunctionModifier.INTEGER, new CastModifier("integer")); //$NON-NLS-1$
convert.addConvert(FunctionModifier.STRING, FunctionModifier.BIGDECIMAL, new CastModifier("decimal(37,5)"));//$NON-NLS-1$
convert.addConvert(FunctionModifier.STRING, FunctionModifier.BIGINTEGER, new CastModifier("numeric(18,0)"));//$NON-NLS-1$
convert.addConvert(FunctionModifier.STRING, FunctionModifier.FLOAT, new CastModifier("float"));//$NON-NLS-1$
convert.addConvert(FunctionModifier.STRING, FunctionModifier.BOOLEAN, new CastModifier("byteint"));//$NON-NLS-1$
convert.addConvert(FunctionModifier.STRING, FunctionModifier.LONG, new CastModifier("numeric(18,0)"));//$NON-NLS-1$
convert.addConvert(FunctionModifier.STRING, FunctionModifier.SHORT, new CastModifier("smallint"));//$NON-NLS-1$
convert.addConvert(FunctionModifier.STRING, FunctionModifier.DOUBLE, new CastModifier("double precision"));//$NON-NLS-1$
convert.addConvert(FunctionModifier.STRING, FunctionModifier.BYTE, new CastModifier("byteint")); //$NON-NLS-1$
convert.addConvert(FunctionModifier.TIMESTAMP, FunctionModifier.STRING, new FunctionModifier() {
@Override
public List<?> translate(Function function) {
return Arrays.asList("cast(cast(", function.getParameters().get(0), " AS FORMAT 'Y4-MM-DDBHH:MI:SSDS(6)') AS VARCHAR(26))"); //$NON-NLS-1$ //$NON-NLS-2$
}
});
convert.addConvert(FunctionModifier.TIME, FunctionModifier.STRING, new FunctionModifier() {
@Override
public List<?> translate(Function function) {
return Arrays.asList("cast(cast(", function.getParameters().get(0), " AS FORMAT 'HH:MI:SS') AS VARCHAR(9))"); //$NON-NLS-1$ //$NON-NLS-2$
}
});
convert.addConvert(FunctionModifier.DATE, FunctionModifier.STRING, new FunctionModifier() {
@Override
public List<?> translate(Function function) {
return Arrays.asList("cast(cast(", function.getParameters().get(0), " AS FORMAT 'YYYY-MM-DD') AS VARCHAR(11))"); //$NON-NLS-1$ //$NON-NLS-2$
}
});
convert.addTypeMapping("varchar(4000)", FunctionModifier.STRING); //$NON-NLS-1$
convert.addNumericBooleanConversions();
registerFunctionModifier(SourceSystemFunctions.CONVERT, convert);
registerFunctionModifier(SourceSystemFunctions.SUBSTRING, new AliasModifier("substr")); //$NON-NLS-1$
registerFunctionModifier(SourceSystemFunctions.LOG, new AliasModifier("LN")); //$NON-NLS-1$
registerFunctionModifier(SourceSystemFunctions.LCASE, new AliasModifier("LOWER")); //$NON-NLS-1$
registerFunctionModifier(SourceSystemFunctions.UCASE, new AliasModifier("UPPER")); //$NON-NLS-1$
registerFunctionModifier(SourceSystemFunctions.LENGTH, new AliasModifier("character_length")); //$NON-NLS-1$
registerFunctionModifier(SourceSystemFunctions.CURDATE, new AliasModifier("CURRENT_DATE")); //$NON-NLS-1$
registerFunctionModifier(SourceSystemFunctions.CURTIME, new AliasModifier("CURRENT_TIME")); //$NON-NLS-1$
registerFunctionModifier(SourceSystemFunctions.YEAR, new ExtractModifier("YEAR")); //$NON-NLS-1$
registerFunctionModifier(SourceSystemFunctions.MONTH, new ExtractModifier("MONTH")); //$NON-NLS-1$
registerFunctionModifier(SourceSystemFunctions.DAYOFMONTH, new ExtractModifier("DAY")); //$NON-NLS-1$
registerFunctionModifier(SourceSystemFunctions.HOUR, new ExtractModifier("HOUR")); //$NON-NLS-1$
registerFunctionModifier(SourceSystemFunctions.MINUTE, new ExtractModifier("MINUTE")); //$NON-NLS-1$
registerFunctionModifier(SourceSystemFunctions.SECOND, new ExtractModifier("SECOND")); //$NON-NLS-1$
registerFunctionModifier(SourceSystemFunctions.LOCATE, new LocateModifier(this.convert));
registerFunctionModifier(SourceSystemFunctions.LEFT, new LeftOrRightFunctionModifier(getLanguageFactory(), this.convert));
registerFunctionModifier(SourceSystemFunctions.RIGHT, new LeftOrRightFunctionModifier(getLanguageFactory(), this.convert));
registerFunctionModifier(SourceSystemFunctions.COT, new FunctionModifier() {
@Override
public List<?> translate(Function function) {
function.setName(SourceSystemFunctions.TAN);
return Arrays.asList(getLanguageFactory().createFunction(SourceSystemFunctions.DIVIDE_OP, new Expression[] {new Literal(1, TypeFacility.RUNTIME_TYPES.INTEGER), function}, TypeFacility.RUNTIME_TYPES.DOUBLE));
}
});
registerFunctionModifier(SourceSystemFunctions.LTRIM, new FunctionModifier() {
@Override
public List<?> translate(Function function) {
ArrayList<Object> target = new ArrayList<Object>();
target.add("TRIM(LEADING FROM ");//$NON-NLS-1$
target.add(function.getParameters().get(0));
target.add(")"); //$NON-NLS-1$
return target;
}
});
registerFunctionModifier(SourceSystemFunctions.RTRIM, new FunctionModifier() {
@Override
public List<?> translate(Function function) {
ArrayList<Object> target = new ArrayList<Object>();
target.add("TRIM(TRAILING FROM ");//$NON-NLS-1$
target.add(function.getParameters().get(0));
target.add(")"); //$NON-NLS-1$
return target;
}
});
registerFunctionModifier(SourceSystemFunctions.MOD, new FunctionModifier() {
@Override
public List<?> translate(Function function) {
return Arrays.asList(function.getParameters().get(0), " MOD ", function.getParameters().get(1)); //$NON-NLS-1$
}
});
addPushDownFunction(TERADATA, "COSH", FLOAT, FLOAT); //$NON-NLS-1$
addPushDownFunction(TERADATA, "TANH", FLOAT, FLOAT); //$NON-NLS-1$
addPushDownFunction(TERADATA, "ACOSH", FLOAT, FLOAT); //$NON-NLS-1$
addPushDownFunction(TERADATA, "ASINH", FLOAT, FLOAT); //$NON-NLS-1$
addPushDownFunction(TERADATA, "ATANH", FLOAT, FLOAT); //$NON-NLS-1$
addPushDownFunction(TERADATA, "CHAR2HEXINT", STRING, STRING); //$NON-NLS-1$
addPushDownFunction(TERADATA, "INDEX", INTEGER, STRING, STRING); //$NON-NLS-1$
addPushDownFunction(TERADATA, "BYTES", INTEGER, STRING); //$NON-NLS-1$
addPushDownFunction(TERADATA, "OCTET_LENGTH", INTEGER, STRING); //$NON-NLS-1$
addPushDownFunction(TERADATA, "HASHAMP", INTEGER, STRING); //$NON-NLS-1$
addPushDownFunction(TERADATA, "HASHBAKAMP", INTEGER, STRING); //$NON-NLS-1$
addPushDownFunction(TERADATA, "HASHBUCKET", INTEGER, STRING); //$NON-NLS-1$
addPushDownFunction(TERADATA, "HASHROW", INTEGER, STRING); //$NON-NLS-1$
addPushDownFunction(TERADATA, "NULLIFZERO", BIG_DECIMAL, BIG_DECIMAL); //$NON-NLS-1$
addPushDownFunction(TERADATA, "ZEROIFNULL", BIG_DECIMAL, BIG_DECIMAL); //$NON-NLS-1$
registerFunctionModifier(SourceSystemFunctions.COT, new FunctionModifier() {
@Override
public List<?> translate(Function function) {
function.setName(SourceSystemFunctions.TAN);
return Arrays.asList(getLanguageFactory().createFunction(SourceSystemFunctions.DIVIDE_OP, new Expression[] {new Literal(1, TypeFacility.RUNTIME_TYPES.INTEGER), function}, TypeFacility.RUNTIME_TYPES.DOUBLE));
}
});
registerFunctionModifier(SourceSystemFunctions.LTRIM, new FunctionModifier() {
@Override
public List<?> translate(Function function) {
return Arrays.asList("TRIM(LEADING FROM ", function.getParameters().get(0), ")"); //$NON-NLS-1$ //$NON-NLS-2$
}
});
registerFunctionModifier(SourceSystemFunctions.RTRIM, new FunctionModifier() {
@Override
public List<?> translate(Function function) {
return Arrays.asList("TRIM(TRAILING FROM ", function.getParameters().get(0), ")"); //$NON-NLS-1$ //$NON-NLS-2$
}
});
registerFunctionModifier(SourceSystemFunctions.MOD, new FunctionModifier() {
@Override
public List<?> translate(Function function) {
return Arrays.asList(function.getParameters().get(0), " MOD ", function.getParameters().get(1)); //$NON-NLS-1$
}
});
}
@Override
public SQLConversionVisitor getSQLConversionVisitor() {
return new TeradataSQLConversionVisitor(this);
}
@Override
public List<String> getSupportedFunctions() {
List<String> supportedFunctions = new ArrayList<String>();
supportedFunctions.addAll(super.getSupportedFunctions());
supportedFunctions.add(SourceSystemFunctions.ABS);
supportedFunctions.add(SourceSystemFunctions.ACOS);
supportedFunctions.add(SourceSystemFunctions.ASIN);
supportedFunctions.add(SourceSystemFunctions.ATAN);
supportedFunctions.add(SourceSystemFunctions.ATAN2);
supportedFunctions.add(SourceSystemFunctions.COALESCE);
supportedFunctions.add(SourceSystemFunctions.COS);
supportedFunctions.add(SourceSystemFunctions.COT);
supportedFunctions.add(SourceSystemFunctions.CONVERT);
supportedFunctions.add(SourceSystemFunctions.CURDATE);
supportedFunctions.add(SourceSystemFunctions.CURTIME);
supportedFunctions.add(SourceSystemFunctions.DAYOFMONTH);
supportedFunctions.add(SourceSystemFunctions.EXP);
supportedFunctions.add(SourceSystemFunctions.HOUR);
supportedFunctions.add(SourceSystemFunctions.LEFT);
supportedFunctions.add(SourceSystemFunctions.LOCATE);
supportedFunctions.add(SourceSystemFunctions.LOG);
supportedFunctions.add(SourceSystemFunctions.LCASE);
supportedFunctions.add(SourceSystemFunctions.LTRIM);
supportedFunctions.add(SourceSystemFunctions.LENGTH);
supportedFunctions.add(SourceSystemFunctions.MINUTE);
supportedFunctions.add(SourceSystemFunctions.MOD);
supportedFunctions.add(SourceSystemFunctions.MONTH);
supportedFunctions.add(SourceSystemFunctions.NULLIF);
supportedFunctions.add(SourceSystemFunctions.RIGHT);
supportedFunctions.add(SourceSystemFunctions.RTRIM);
supportedFunctions.add(SourceSystemFunctions.SECOND);
supportedFunctions.add(SourceSystemFunctions.SIN);
supportedFunctions.add(SourceSystemFunctions.SQRT);
supportedFunctions.add(SourceSystemFunctions.SUBSTRING);
supportedFunctions.add(SourceSystemFunctions.TAN);
supportedFunctions.add(SourceSystemFunctions.TRIM);
supportedFunctions.add(SourceSystemFunctions.UCASE);
supportedFunctions.add(SourceSystemFunctions.YEAR);
return supportedFunctions;
}
@Override
public String translateLiteralDate(Date dateValue) {
return "DATE '" + formatDateValue(dateValue, false) + '\''; //$NON-NLS-1$
}
@Override
public String translateLiteralTime(Time timeValue) {
return "TIME '" + formatDateValue(timeValue, false) + '\''; //$NON-NLS-1$
}
@Override
public String translateLiteralTimestamp(Timestamp timestampValue) {
return "TIMESTAMP '" + formatDateValue(timestampValue, false) + '\''; //$NON-NLS-1$
}
// Teradata also supports MINUS & ALL set operators
// more aggregates available
@Override
public boolean supportsScalarSubqueries() {
return false;
}
@Override
public boolean supportsUnions() {
return true;
}
@Override
public boolean supportsIntersect() {
return true;
}
@Override
public boolean supportsExcept() {
return true;
}
@Override
public boolean supportsInlineViews() {
return true;
}
@Override
public boolean supportsAggregatesEnhancedNumeric() {
return true;
}
@Override
public boolean supportsCommonTableExpressions() {
return false;
}
@Override
public NullOrder getDefaultNullOrder() {
return NullOrder.FIRST;
}
@Override
public List<?> translateCommand(Command command, ExecutionContext context) {
if (command instanceof QueryExpression) {
QueryExpression qe = (QueryExpression)command;
//teradata prefers positional ordering
if (qe.getOrderBy() != null) {
Select select = qe.getProjectedQuery();
List<DerivedColumn> derivedColumns = select.getDerivedColumns();
Map<String, Integer> positions = new HashMap<String, Integer>();
int i = 1;
for (DerivedColumn derivedColumn : derivedColumns) {
String name = derivedColumn.getAlias();
if (name == null && derivedColumn.getExpression() instanceof ColumnReference) {
ColumnReference cr = (ColumnReference)derivedColumn.getExpression();
name = cr.toString();
}
positions.put(name, i++);
}
for (SortSpecification ss : qe.getOrderBy().getSortSpecifications()) {
Expression ex = ss.getExpression();
if (!(ex instanceof ColumnReference)) {
continue;
}
ColumnReference cr = (ColumnReference)ex;
Integer position = positions.get(cr.toString());
if (position != null) {
ss.setExpression(new Literal(position, TypeFacility.RUNTIME_TYPES.INTEGER));
}
}
}
}
return super.translateCommand(command, context);
}
public static class LocateModifier extends FunctionModifier {
ConvertModifier convertModifier;
public LocateModifier(ConvertModifier convertModifier) {
this.convertModifier = convertModifier;
}
@Override
public List<?> translate(Function function) {
ArrayList<Object> target = new ArrayList<Object>();
Expression expr1 = function.getParameters().get(0);
Expression expr2 = function.getParameters().get(1);
if (function.getParameters().size() > 2) {
Expression expr3 = function.getParameters().get(2);
target.add("position("); //$NON-NLS-1$
target.add(expr1);
target.add( " in "); //$NON-NLS-1$
target.add("substr("); //$NON-NLS-1$
target.add(expr2);
target.add(","); //$NON-NLS-1$
target.add(expr3);
target.add("))"); //$NON-NLS-1$
}
else {
target.add("position("); //$NON-NLS-1$
target.add(expr1);
target.add( " in "); //$NON-NLS-1$
target.add(expr2);
target.add(")"); //$NON-NLS-1$
}
return target;
}
}
public static class ExtractModifier extends FunctionModifier {
private String type;
public ExtractModifier(String type) {
this.type = type;
}
@Override
public List<?> translate(Function function) {
return Arrays.asList("extract(",this.type," from ",function.getParameters().get(0) ,")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
}
public static class CastModifier extends FunctionModifier {
private String target;
public CastModifier(String target) {
this.target = target;
}
@Override
public List<?> translate(Function function) {
return Arrays.asList("cast(", function.getParameters().get(0), " AS "+this.target+")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
}
public static class LeftOrRightFunctionModifier extends FunctionModifier {
private LanguageFactory langFactory;
ConvertModifier convertModifier;
public LeftOrRightFunctionModifier(LanguageFactory langFactory, ConvertModifier converModifier) {
this.langFactory = langFactory;
this.convertModifier = converModifier;
}
@Override
public List<?> translate(Function function) {
List<Expression> args = function.getParameters();
ArrayList<Object> target = new ArrayList<Object>();
if (function.getName().equalsIgnoreCase("left")) { //$NON-NLS-1$
//substr(string, 1, length)
target.add("substr("); //$NON-NLS-1$
target.add(args.get(0));
target.add(","); //$NON-NLS-1$
target.add(langFactory.createLiteral(Integer.valueOf(1), TypeFacility.RUNTIME_TYPES.INTEGER));
target.add(","); //$NON-NLS-1$
target.add(args.get(1));
target.add(")"); //$NON-NLS-1$
} else if (function.getName().equalsIgnoreCase("right")) { //$NON-NLS-1$
//substr(case_size, character_length(case_size) -4)
target.add("substr("); //$NON-NLS-1$
target.add(args.get(0));
target.add(",(character_length("); //$NON-NLS-1$
target.add(args.get(0));
target.add(")-"); //$NON-NLS-1$
target.add(args.get(1));
target.add("+1))"); //$NON-NLS-1$ // offset for 1 based index
}
return target;
}
}
@Override
protected boolean usesDatabaseVersion() {
return true;
}
@Override
public String getHibernateDialectClassName() {
if (getVersion().getMajorVersion() >= 14) {
return "org.hibernate.dialect.Teradata14Dialect"; //$NON-NLS-1$
}
return "org.hibernate.dialect.TeradataDialect"; //$NON-NLS-1$
}
@Override
protected boolean supportsBooleanExpressions() {
return false;
}
}