blob: f72d6ee1d837171d30ab51e0e72ecc384a371933 [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 com.google.common.base.Preconditions;
import org.apache.tajo.catalog.Column;
import org.apache.tajo.exception.TajoRuntimeException;
import org.apache.tajo.exception.UnsupportedException;
import org.apache.tajo.plan.Target;
import org.apache.tajo.plan.expr.EvalNode;
import org.apache.tajo.plan.logical.*;
import org.apache.tajo.util.StringUtils;
import javax.annotation.Nullable;
import java.sql.DatabaseMetaData;
import java.util.Stack;
/**
* Generator to build a SQL statement from a plan fragment
*/
public class SQLBuilder {
@SuppressWarnings("unused")
private final DatabaseMetaData dbMetaData;
private final SQLExpressionGenerator sqlExprGen;
public static class SQLBuilderContext {
StringBuilder sb;
}
public SQLBuilder(DatabaseMetaData dbMetaData, SQLExpressionGenerator exprGen) {
this.dbMetaData = dbMetaData;
this.sqlExprGen = exprGen;
}
public String build(String tableName, Column [] targets, @Nullable EvalNode filter, @Nullable Long limit) {
StringBuilder selectClause = new StringBuilder("SELECT ");
if (targets.length > 0) {
selectClause.append(StringUtils.join(targets, ",", new Function<Column, String>() {
@Override
public String apply(@Nullable Column input) {
return input.getSimpleName();
}
}));
} else {
selectClause.append("1");
}
selectClause.append(" ");
StringBuilder fromClause = new StringBuilder("FROM ");
fromClause.append(tableName).append(" ");
StringBuilder whereClause = null;
if (filter != null) {
whereClause = new StringBuilder("WHERE ");
whereClause.append(sqlExprGen.generate(filter)).append(" ");
}
StringBuilder limitClause = null;
if (limit != null) {
limitClause = new StringBuilder("LIMIT ");
limitClause.append(limit).append(" ");
}
return generateSelectStmt(selectClause, fromClause, whereClause, limitClause);
}
public String generateSelectStmt(StringBuilder selectClause,
StringBuilder fromClause,
@Nullable StringBuilder whereClause,
@Nullable StringBuilder limitClause) {
return
selectClause.toString() +
fromClause.toString() +
(whereClause != null ? whereClause.toString() : "") +
(limitClause != null ? limitClause.toString() : "");
}
public String build(LogicalNode planPart) {
SQLBuilderContext context = new SQLBuilderContext();
visit(context, planPart, new Stack<LogicalNode>());
return context.sb.toString();
}
public void visit(SQLBuilderContext context, LogicalNode node, Stack<LogicalNode> stack) {
stack.push(node);
switch (node.getType()) {
case SCAN:
visitScan(context, (ScanNode) node, stack);
break;
case GROUP_BY:
visitGroupBy(context, (GroupbyNode) node, stack);
break;
case SELECTION:
visitFilter(context, (SelectionNode) node, stack);
break;
case PROJECTION:
visitProjection(context, (ProjectionNode) node, stack);
break;
case TABLE_SUBQUERY:
visitDerivedSubquery(context, (TableSubQueryNode) node, stack);
break;
default:
throw new TajoRuntimeException(new UnsupportedException("plan node '" + node.getType().name() + "'"));
}
stack.pop();
}
public void visitDerivedSubquery(SQLBuilderContext ctx, TableSubQueryNode derivedSubquery, Stack<LogicalNode> stack) {
ctx.sb.append(" (");
visit(ctx, derivedSubquery.getSubQuery(), stack);
ctx.sb.append(" ) ").append(derivedSubquery.getTableName());
}
public void visitProjection(SQLBuilderContext ctx, ProjectionNode projection, Stack<LogicalNode> stack) {
visit(ctx, projection.getChild(), stack);
}
public void visitGroupBy(SQLBuilderContext ctx, GroupbyNode groupby, Stack<LogicalNode> stack) {
visit(ctx, groupby.getChild(), stack);
ctx.sb.append("GROUP BY ").append(StringUtils.join(groupby.getGroupingColumns(), ",", 0)).append(" ");
}
public void visitFilter(SQLBuilderContext ctx, SelectionNode filter, Stack<LogicalNode> stack) {
visit(ctx, filter.getChild(), stack);
ctx.sb.append("WHERE " + sqlExprGen.generate(filter.getQual()));
}
public void visitScan(SQLBuilderContext ctx, ScanNode scan, Stack<LogicalNode> stack) {
StringBuilder selectClause = new StringBuilder("SELECT ");
if (scan.getTargets().length > 0) {
selectClause.append(generateTargetList(scan.getTargets()));
} else {
selectClause.append("1");
}
selectClause.append(" ");
ctx.sb.append("FROM ").append(scan.getTableName()).append(" ");
if (scan.hasAlias()) {
ctx.sb.append("AS ").append(scan.getAlias()).append(" ");
}
if (scan.hasQual()) {
ctx.sb.append("WHERE " + sqlExprGen.generate(scan.getQual()));
}
}
public String generateTargetList(Target [] targets) {
return StringUtils.join(targets, ",", new Function<Target, String>() {
@Override
public String apply(@Nullable Target t) {
StringBuilder sb = new StringBuilder(sqlExprGen.generate(t.getEvalTree()));
if (t.hasAlias()) {
sb.append(" AS ").append(t.getAlias());
}
return sb.toString();
}
});
}
}