blob: da97e32e9223eced77cbe1734e8456bf203dd647 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tajo.storage.jdbc;
import com.google.common.base.Function;
import org.apache.tajo.datum.Datum;
import org.apache.tajo.exception.NotImplementedException;
import org.apache.tajo.exception.TajoRuntimeException;
import org.apache.tajo.exception.UnsupportedDataTypeException;
import org.apache.tajo.plan.expr.*;
import org.apache.tajo.schema.IdentifierUtil;
import org.apache.tajo.type.Type;
import org.apache.tajo.util.StringUtils;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.Stack;
/**
* A generator to build a SQL representation from a sql expression
*/
public class SQLExpressionGenerator extends SimpleEvalNodeVisitor<SQLExpressionGenerator.Context> {
final private DatabaseMetaData dbMetaData;
private final String LITERAL_QUOTE = "'";
@SuppressWarnings("unused")
private final String DEFAULT_LITERAL_QUOTE = "'";
@SuppressWarnings("unused")
private String IDENTIFIER_QUOTE = "\"";
private final String DEFAULT_IDENTIFIER_QUOTE = "\"";
public SQLExpressionGenerator(DatabaseMetaData dbMetaData) {
this.dbMetaData = dbMetaData;
initDatabaseDependentSQLRepr();
}
private void initDatabaseDependentSQLRepr() {
String quoteStr = null;
try {
quoteStr = dbMetaData.getIdentifierQuoteString();
} catch (SQLException e) {
}
this.IDENTIFIER_QUOTE = quoteStr != null ? quoteStr : DEFAULT_IDENTIFIER_QUOTE;
}
public String quote(String text) {
return LITERAL_QUOTE + text + LITERAL_QUOTE;
}
public String generate(EvalNode node) {
Context context = new Context();
visit(context, node, new Stack<EvalNode>());
return context.sb.toString();
}
public static class Context {
StringBuilder sb = new StringBuilder();
public void append(String text) {
sb.append(text).append(" ");
}
}
@Override
protected EvalNode visitUnaryEval(Context context, UnaryEval unary, Stack<EvalNode> stack) {
switch (unary.getType()) {
case NOT:
context.sb.append("NOT ");
super.visitUnaryEval(context, unary, stack);
break;
case SIGNED:
SignedEval signed = (SignedEval) unary;
if (signed.isNegative()) {
context.sb.append("-");
}
super.visitUnaryEval(context, unary, stack);
break;
case IS_NULL:
super.visitUnaryEval(context, unary, stack);
IsNullEval isNull = (IsNullEval) unary;
if (isNull.isNot()) {
context.sb.append("IS NOT NULL ");
} else {
context.sb.append("IS NULL ");
}
break;
case CAST:
super.visitUnaryEval(context, unary, stack);
context.sb.append(" AS ").append(convertTajoTypeToSQLType(unary.getValueType()));
}
return unary;
}
@Override
protected EvalNode visitBinaryEval(Context context, Stack<EvalNode> stack, BinaryEval binaryEval) {
stack.push(binaryEval);
visit(context, binaryEval.getLeftExpr(), stack);
context.sb.append(convertBinOperatorToSQLRepr(binaryEval.getType())).append(" ");
visit(context, binaryEval.getRightExpr(), stack);
stack.pop();
return binaryEval;
}
@Override
protected EvalNode visitConst(Context context, ConstEval constant, Stack<EvalNode> stack) {
context.sb.append(convertDatumToSQLLiteral(constant.getValue())).append(" ");
return constant;
}
protected EvalNode visitRowConstant(Context context, RowConstantEval row, Stack<EvalNode> stack) {
StringBuilder sb = new StringBuilder("(");
sb.append(StringUtils.join(row.getValues(), ",", new Function<Datum, String>() {
@Override
public String apply(Datum v) {
return convertDatumToSQLLiteral(v);
}
}));
sb.append(")");
context.append(sb.toString());
return row;
}
@Override
protected EvalNode visitField(Context context, FieldEval field, Stack<EvalNode> stack) {
// strip the database name
String tableName;
if (IdentifierUtil.isSimpleIdentifier(field.getQualifier())) {
tableName = field.getQualifier();
} else {
tableName = IdentifierUtil.extractSimpleName(field.getQualifier());
}
context.append(IdentifierUtil.buildFQName(tableName, field.getColumnName()));
return field;
}
@Override
protected EvalNode visitBetween(Context context, BetweenPredicateEval evalNode, Stack<EvalNode> stack) {
stack.push(evalNode);
visit(context, evalNode.getPredicand(), stack);
context.append("BETWEEN");
visit(context, evalNode.getBegin(), stack);
context.append("AND");
visit(context, evalNode.getEnd(), stack);
return evalNode;
}
@Override
protected EvalNode visitCaseWhen(Context context, CaseWhenEval evalNode, Stack<EvalNode> stack) {
stack.push(evalNode);
context.append("CASE");
for (CaseWhenEval.IfThenEval ifThenEval : evalNode.getIfThenEvals()) {
visitIfThen(context, ifThenEval, stack);
}
context.append("ELSE");
if (evalNode.hasElse()) {
visit(context, evalNode.getElse(), stack);
}
stack.pop();
context.append("END");
return evalNode;
}
@Override
protected EvalNode visitIfThen(Context context, CaseWhenEval.IfThenEval evalNode, Stack<EvalNode> stack) {
stack.push(evalNode);
context.append("WHEN");
visit(context, evalNode.getCondition(), stack);
context.append("THEN");
visit(context, evalNode.getResult(), stack);
stack.pop();
return evalNode;
}
@Override
protected EvalNode visitFuncCall(Context context, FunctionEval func, Stack<EvalNode> stack) {
// TODO - TAJO-1837 should be resolved if we support RDBMS functions better.
stack.push(func);
context.sb.append(func.getName()).append("(");
boolean first = true;
for (EvalNode param : func.getArgs()) {
if (first) {
first = false;
} else {
context.sb.append(",");
}
visit(context, param, stack);
}
context.sb.append(")");
stack.pop();
return func;
}
@Override
protected EvalNode visitSubquery(Context context, SubqueryEval evalNode, Stack<EvalNode> stack) {
throw new TajoRuntimeException(new NotImplementedException());
}
/**
* convert Tajo literal into SQL representation
*
* @param d Datum
*/
public String convertDatumToSQLLiteral(Datum d) {
switch (d.type()) {
case BOOLEAN:
return d.asBool() ? "TRUE" : "FALSE";
case INT1:
case INT2:
case INT4:
case INT8:
case FLOAT4:
case FLOAT8:
case NUMERIC:
return d.asChars();
case TEXT:
case VARCHAR:
case CHAR:
return quote(d.asChars());
case DATE:
return "DATE " + quote(d.asChars());
case TIME:
return "TIME " + quote(d.asChars());
case TIMESTAMP:
return "TIMESTAMP " + quote(d.asChars());
case NULL_TYPE:
return "NULL";
default:
throw new TajoRuntimeException(new UnsupportedDataTypeException(d.type().name()));
}
}
/**
* Convert Tajo DataType into SQL DataType
*
* @param dataType Tajo DataType
* @return SQL DataType
*/
public String convertTajoTypeToSQLType(Type dataType) {
switch (dataType.kind()) {
case INT1:
return "TINYINT";
case INT2:
return "SMALLINT";
case INT4:
return "INTEGER";
case INT8:
return "BIGINT";
case FLOAT4:
return "FLOAT";
case FLOAT8:
return "DOUBLE";
default:
return dataType.kind().name();
}
}
/**
* Convert EvalType the operator notation into SQL notation
*
* @param op EvalType
* @return SQL representation
*/
public String convertBinOperatorToSQLRepr(EvalType op) {
return op.getOperatorName();
}
}