Skip to content

Commit

Permalink
Implement AST -> SQL printer
Browse files Browse the repository at this point in the history
  • Loading branch information
martint committed Oct 18, 2012
1 parent ef811d2 commit 75eca37
Show file tree
Hide file tree
Showing 2 changed files with 333 additions and 0 deletions.
144 changes: 144 additions & 0 deletions src/main/java/com/facebook/presto/sql/ExpressionFormatter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package com.facebook.presto.sql;

import com.facebook.presto.sql.tree.AliasedExpression;
import com.facebook.presto.sql.tree.AllColumns;
import com.facebook.presto.sql.tree.ArithmeticExpression;
import com.facebook.presto.sql.tree.AstVisitor;
import com.facebook.presto.sql.tree.ComparisonExpression;
import com.facebook.presto.sql.tree.DoubleLiteral;
import com.facebook.presto.sql.tree.Expression;
import com.facebook.presto.sql.tree.FunctionCall;
import com.facebook.presto.sql.tree.LikePredicate;
import com.facebook.presto.sql.tree.LogicalBinaryExpression;
import com.facebook.presto.sql.tree.LongLiteral;
import com.facebook.presto.sql.tree.Node;
import com.facebook.presto.sql.tree.QualifiedNameReference;
import com.facebook.presto.sql.tree.StringLiteral;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.Iterables;

public class ExpressionFormatter
{
public static String toString(Expression expression)
{
return new Formatter().process(expression, null);
}

public static Function<Expression, String> expressionFormatterFunction()
{
return new Function<Expression, String>()
{
@Override
public String apply(Expression input)
{
return ExpressionFormatter.toString(input);
}
};
}

private static class Formatter
extends AstVisitor<String, Void>
{
@Override
protected String visitNode(Node node, Void context)
{
throw new UnsupportedOperationException();
}

@Override
protected String visitExpression(Expression node, Void context)
{
throw new UnsupportedOperationException("not yet implemented");
}

@Override
protected String visitStringLiteral(StringLiteral node, Void context)
{
return "'" + node.getValue() + "'";
}

@Override
protected String visitLongLiteral(LongLiteral node, Void context)
{
return node.getValue();
}

@Override
protected String visitDoubleLiteral(DoubleLiteral node, Void context)
{
return node.getValue();
}

@Override
protected String visitQualifiedNameReference(QualifiedNameReference node, Void context)
{
return node.getName().toString();
}

@Override
protected String visitAliasedExpression(AliasedExpression node, Void context)
{
return ExpressionFormatter.toString(node.getExpression()) + ' ' + node.getAlias();
}

@Override
protected String visitFunctionCall(FunctionCall node, Void context)
{
return node.getName() + "(" + Joiner.on(", ").join(Iterables.transform(node.getArguments(), expressionFormatterFunction())) + ")";
}

@Override
protected String visitLogicalBinaryExpression(LogicalBinaryExpression node, Void context)
{
return formatBinaryExpression(node.getType().toString(), node.getLeft(), node.getRight());
}

@Override
protected String visitComparisonExpression(ComparisonExpression node, Void context)
{
return formatBinaryExpression(node.getType().getValue(), node.getLeft(), node.getRight());
}

@Override
protected String visitArithmeticExpression(ArithmeticExpression node, Void context)
{
return formatBinaryExpression(node.getType().getValue(), node.getLeft(), node.getRight());
}

@Override
protected String visitLikePredicate(LikePredicate node, Void context)
{
StringBuilder builder = new StringBuilder();

builder.append('(')
.append(ExpressionFormatter.toString(node.getValue()))
.append(" LIKE ")
.append(ExpressionFormatter.toString(node.getPattern()));

if (node.getEscape() != null) {
builder.append(" ESCAPE ")
.append(ExpressionFormatter.toString(node.getEscape()));
}

builder.append(')');

return builder.toString();
}

@Override
protected String visitAllColumns(AllColumns node, Void context)
{
if (node.getPrefix().isPresent()) {
return node.getPrefix().get() + ".*";
}

return "*";
}

private String formatBinaryExpression(String operator, Expression left, Expression right)
{
return '(' + ExpressionFormatter.toString(left) + ' ' + operator + ' ' + ExpressionFormatter.toString(right) + ')';
}
}
}
189 changes: 189 additions & 0 deletions src/main/java/com/facebook/presto/sql/SqlFormatter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
package com.facebook.presto.sql;

import com.facebook.presto.sql.tree.AliasedRelation;
import com.facebook.presto.sql.tree.DefaultTraversalVisitor;
import com.facebook.presto.sql.tree.Node;
import com.facebook.presto.sql.tree.Query;
import com.facebook.presto.sql.tree.Relation;
import com.facebook.presto.sql.tree.Select;
import com.facebook.presto.sql.tree.SortItem;
import com.facebook.presto.sql.tree.Subquery;
import com.facebook.presto.sql.tree.Table;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.collect.Iterables;

import java.util.Iterator;

import static com.facebook.presto.sql.ExpressionFormatter.expressionFormatterFunction;

public class SqlFormatter
{
private static final String INDENT = " ";

public static String toString(Node root)
{
StringBuilder builder = new StringBuilder();
new Formatter(builder).process(root, 0);
return builder.toString();
}

private static class Formatter
extends DefaultTraversalVisitor<Void, Integer>
{
private final StringBuilder builder;

public Formatter(StringBuilder builder)
{
this.builder = builder;
}

@Override
protected Void visitNode(Node node, Integer indent)
{
throw new UnsupportedOperationException("not yet implemented: " + node);
}

@Override
protected Void visitQuery(Query node, Integer indent)
{
process(node.getSelect(), indent);

append(indent, "FROM ");
if (node.getFrom().size() > 1) {
builder.append('\n');
Iterator<Relation> relations = node.getFrom().iterator();
while (relations.hasNext()) {
process(relations.next(), indent);
if (relations.hasNext()) {
builder.append(", ");
}
}
}
else {
process(Iterables.getOnlyElement(node.getFrom()), indent);
}

builder.append('\n');

if (node.getWhere() != null) {
append(indent, "WHERE " + ExpressionFormatter.toString(node.getWhere()))
.append('\n');
}

if (!node.getGroupBy().isEmpty()) {
append(indent, "GROUP BY " + Joiner.on(", ").join(Iterables.transform(node.getGroupBy(), expressionFormatterFunction())))
.append('\n');
}

if (node.getHaving() != null) {
append(indent, "HAVING " + ExpressionFormatter.toString(node.getHaving()))
.append('\n');
}

if (!node.getOrderBy().isEmpty()) {
append(indent, "ORDER BY " + Joiner.on(", ").join(Iterables.transform(node.getOrderBy(), orderByFormatterFunction())));
}

if (node.getLimit() != null) {
append(indent, "LIMIT " + node.getLimit());
}

return null;
}

@Override
protected Void visitSelect(Select node, Integer indent)
{
append(indent, "SELECT ")
.append(Joiner.on(", ").join(Iterables.transform(node.getSelectItems(), expressionFormatterFunction())))
.append('\n');

return null;
}

@Override
protected Void visitTable(Table node, Integer indent)
{
builder.append(node.getName().toString());
return null;
}

@Override
protected Void visitAliasedRelation(AliasedRelation node, Integer indent)
{
if (node.getColumnNames() != null && !node.getColumnNames().isEmpty()) {
throw new UnsupportedOperationException("not yet implemented: relation alias with column mappings"); // TODO
}

process(node.getRelation(), indent);

builder.append(' ')
.append(node.getAlias());

return null;
}

@Override
protected Void visitSubquery(Subquery node, Integer indent)
{
builder.append('(')
.append('\n');

process(node.getQuery(), indent + 1);

append(indent, ")");

return null;
}

private StringBuilder append(int indent, String value)
{
return builder.append(Strings.repeat(INDENT, indent))
.append(value);
}
}

private static Function<SortItem, String> orderByFormatterFunction()
{
return new Function<SortItem, String>()
{
@Override
public String apply(SortItem input)
{
StringBuilder builder = new StringBuilder();

builder.append(ExpressionFormatter.toString(input.getSortKey()))
.append(' ');

switch (input.getOrdering()) {
case ASCENDING:
builder.append("ASC");
break;
case DESCENDING:
builder.append("DESC");
break;
default:
throw new UnsupportedOperationException("unknown ordering: " + input.getOrdering());
}

switch (input.getNullOrdering()) {
case FIRST:
builder.append(" NULLS FIRST");
break;
case LAST:
builder.append(" NULLS LAST");
break;
case UNDEFINED:
// no op
break;
default:
throw new UnsupportedOperationException("unknown null ordering: " + input.getNullOrdering());
}

return builder.toString();
}
};
}
}

0 comments on commit 75eca37

Please sign in to comment.