This is an automated email from the ASF dual-hosted git repository.

jackietien pushed a commit to branch ty/TableModelGrammar
in repository https://gitbox.apache.org/repos/asf/iotdb.git


The following commit(s) were added to refs/heads/ty/TableModelGrammar by this 
push:
     new c00011d0285 Add format
c00011d0285 is described below

commit c00011d0285f55579ac38a401bf2ea10bb2da34e
Author: JackieTien97 <[email protected]>
AuthorDate: Thu Feb 29 16:35:19 2024 +0800

    Add format
---
 .../relational/sql/util/ExpressionFormatter.java   | 621 ++++++++++++++++
 .../iotdb/db/relational/sql/util/SqlFormatter.java | 783 +++++++++++++++++++++
 2 files changed, 1404 insertions(+)

diff --git 
a/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/util/ExpressionFormatter.java
 
b/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/util/ExpressionFormatter.java
index d2fe0cc7e5c..71ef8e51cab 100644
--- 
a/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/util/ExpressionFormatter.java
+++ 
b/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/util/ExpressionFormatter.java
@@ -19,18 +19,87 @@
 
 package org.apache.iotdb.db.relational.sql.util;
 
+import org.apache.iotdb.db.relational.sql.tree.AllColumns;
+import org.apache.iotdb.db.relational.sql.tree.AllRows;
+import org.apache.iotdb.db.relational.sql.tree.ArithmeticBinaryExpression;
+import org.apache.iotdb.db.relational.sql.tree.ArithmeticUnaryExpression;
 import org.apache.iotdb.db.relational.sql.tree.AstVisitor;
+import org.apache.iotdb.db.relational.sql.tree.BetweenPredicate;
+import org.apache.iotdb.db.relational.sql.tree.BinaryLiteral;
+import org.apache.iotdb.db.relational.sql.tree.BooleanLiteral;
+import org.apache.iotdb.db.relational.sql.tree.Cast;
+import org.apache.iotdb.db.relational.sql.tree.CoalesceExpression;
+import org.apache.iotdb.db.relational.sql.tree.ComparisonExpression;
+import org.apache.iotdb.db.relational.sql.tree.CurrentDatabase;
+import org.apache.iotdb.db.relational.sql.tree.CurrentTime;
+import org.apache.iotdb.db.relational.sql.tree.CurrentUser;
+import org.apache.iotdb.db.relational.sql.tree.DecimalLiteral;
+import org.apache.iotdb.db.relational.sql.tree.DereferenceExpression;
+import org.apache.iotdb.db.relational.sql.tree.DoubleLiteral;
+import org.apache.iotdb.db.relational.sql.tree.ExistsPredicate;
 import org.apache.iotdb.db.relational.sql.tree.Expression;
+import org.apache.iotdb.db.relational.sql.tree.FieldReference;
+import org.apache.iotdb.db.relational.sql.tree.FunctionCall;
+import org.apache.iotdb.db.relational.sql.tree.GenericDataType;
+import org.apache.iotdb.db.relational.sql.tree.GenericLiteral;
+import org.apache.iotdb.db.relational.sql.tree.GroupingElement;
+import org.apache.iotdb.db.relational.sql.tree.GroupingSets;
+import org.apache.iotdb.db.relational.sql.tree.Identifier;
+import org.apache.iotdb.db.relational.sql.tree.IfExpression;
+import org.apache.iotdb.db.relational.sql.tree.InListExpression;
+import org.apache.iotdb.db.relational.sql.tree.InPredicate;
+import org.apache.iotdb.db.relational.sql.tree.IsNotNullPredicate;
+import org.apache.iotdb.db.relational.sql.tree.IsNullPredicate;
+import org.apache.iotdb.db.relational.sql.tree.LikePredicate;
 import org.apache.iotdb.db.relational.sql.tree.Literal;
+import org.apache.iotdb.db.relational.sql.tree.LogicalExpression;
+import org.apache.iotdb.db.relational.sql.tree.LongLiteral;
+import org.apache.iotdb.db.relational.sql.tree.Node;
+import org.apache.iotdb.db.relational.sql.tree.NotExpression;
+import org.apache.iotdb.db.relational.sql.tree.NullIfExpression;
+import org.apache.iotdb.db.relational.sql.tree.NullLiteral;
+import org.apache.iotdb.db.relational.sql.tree.NumericParameter;
+import org.apache.iotdb.db.relational.sql.tree.OrderBy;
+import org.apache.iotdb.db.relational.sql.tree.Parameter;
+import org.apache.iotdb.db.relational.sql.tree.QualifiedName;
+import org.apache.iotdb.db.relational.sql.tree.QuantifiedComparisonExpression;
+import org.apache.iotdb.db.relational.sql.tree.Row;
+import org.apache.iotdb.db.relational.sql.tree.SearchedCaseExpression;
+import org.apache.iotdb.db.relational.sql.tree.SimpleCaseExpression;
+import org.apache.iotdb.db.relational.sql.tree.SimpleGroupBy;
+import org.apache.iotdb.db.relational.sql.tree.SortItem;
+import org.apache.iotdb.db.relational.sql.tree.StringLiteral;
+import org.apache.iotdb.db.relational.sql.tree.SubqueryExpression;
 import org.apache.iotdb.db.relational.sql.tree.SymbolReference;
+import org.apache.iotdb.db.relational.sql.tree.Trim;
+import org.apache.iotdb.db.relational.sql.tree.TypeParameter;
+import org.apache.iotdb.db.relational.sql.tree.WhenClause;
 
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.util.List;
+import java.util.Locale;
 import java.util.Optional;
 import java.util.function.Function;
 
+import static com.google.common.collect.Iterables.getOnlyElement;
 import static java.util.Objects.requireNonNull;
+import static java.util.stream.Collectors.joining;
+import static java.util.stream.Collectors.toList;
+import static 
org.apache.iotdb.db.relational.sql.util.ReservedIdentifiers.reserved;
+import static org.apache.iotdb.db.relational.sql.util.SqlFormatter.formatName;
+import static org.apache.iotdb.db.relational.sql.util.SqlFormatter.formatSql;
 
 public final class ExpressionFormatter {
 
+  private static final ThreadLocal<DecimalFormat> doubleFormatter =
+      ThreadLocal.withInitial(
+          () ->
+              new DecimalFormat("0.###################E0###", new 
DecimalFormatSymbols(Locale.US)));
+
   private ExpressionFormatter() {}
 
   public static String formatExpression(Expression expression) {
@@ -48,5 +117,557 @@ public final class ExpressionFormatter {
       this.symbolReferenceFormatter =
           requireNonNull(symbolReferenceFormatter, "symbolReferenceFormatter 
is null");
     }
+
+    @Override
+    protected String visitNode(Node node, Void context) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    protected String visitRow(Row node, Void context) {
+      return node.getItems().stream()
+          .map(child -> process(child, context))
+          .collect(joining(", ", "ROW (", ")"));
+    }
+
+    @Override
+    protected String visitExpression(Expression node, Void context) {
+      throw new UnsupportedOperationException(
+          String.format(
+              "not yet implemented: %s.visit%s",
+              getClass().getName(), node.getClass().getSimpleName()));
+    }
+
+    @Override
+    protected String visitCurrentDatabase(CurrentDatabase node, Void context) {
+      return "CURRENT_DATABASE";
+    }
+
+    @Override
+    protected String visitCurrentUser(CurrentUser node, Void context) {
+      return "CURRENT_USER";
+    }
+
+    @Override
+    protected String visitTrim(Trim node, Void context) {
+      if (!node.getTrimCharacter().isPresent()) {
+        return String.format(
+            "trim(%s FROM %s)", node.getSpecification(), 
process(node.getTrimSource(), context));
+      }
+
+      return String.format(
+          "trim(%s %s FROM %s)",
+          node.getSpecification(),
+          process(node.getTrimCharacter().get(), context),
+          process(node.getTrimSource(), context));
+    }
+
+    @Override
+    protected String visitCurrentTime(CurrentTime node, Void context) {
+      StringBuilder builder = new StringBuilder();
+
+      builder.append(node.getFunction().getName());
+
+      if (node.getPrecision().isPresent()) {
+        builder.append('(').append(node.getPrecision()).append(')');
+      }
+
+      return builder.toString();
+    }
+
+    @Override
+    protected String visitBooleanLiteral(BooleanLiteral node, Void context) {
+      return literalFormatter
+          .map(formatter -> formatter.apply(node))
+          .orElseGet(() -> String.valueOf(node.getValue()));
+    }
+
+    @Override
+    protected String visitStringLiteral(StringLiteral node, Void context) {
+      return literalFormatter
+          .map(formatter -> formatter.apply(node))
+          .orElseGet(() -> formatStringLiteral(node.getValue()));
+    }
+
+    @Override
+    protected String visitBinaryLiteral(BinaryLiteral node, Void context) {
+      return literalFormatter
+          .map(formatter -> formatter.apply(node))
+          .orElseGet(() -> "X'" + node.toHexString() + "'");
+    }
+
+    @Override
+    protected String visitParameter(Parameter node, Void context) {
+      return "?";
+    }
+
+    @Override
+    protected String visitAllRows(AllRows node, Void context) {
+      return "ALL";
+    }
+
+    @Override
+    protected String visitLongLiteral(LongLiteral node, Void context) {
+      return literalFormatter.map(formatter -> 
formatter.apply(node)).orElseGet(node::getValue);
+    }
+
+    @Override
+    protected String visitDoubleLiteral(DoubleLiteral node, Void context) {
+      return literalFormatter
+          .map(formatter -> formatter.apply(node))
+          .orElseGet(() -> doubleFormatter.get().format(node.getValue()));
+    }
+
+    @Override
+    protected String visitDecimalLiteral(DecimalLiteral node, Void context) {
+      return literalFormatter
+          .map(formatter -> formatter.apply(node))
+          // TODO return node value without "DECIMAL '..'" when
+          // FeaturesConfig#parseDecimalLiteralsAsDouble switch is removed
+          .orElseGet(() -> "DECIMAL '" + node.getValue() + "'");
+    }
+
+    @Override
+    protected String visitGenericLiteral(GenericLiteral node, Void context) {
+      return literalFormatter
+          .map(formatter -> formatter.apply(node))
+          .orElseGet(() -> node.getType() + " " + 
formatStringLiteral(node.getValue()));
+    }
+
+    @Override
+    protected String visitNullLiteral(NullLiteral node, Void context) {
+      return literalFormatter.map(formatter -> 
formatter.apply(node)).orElse("null");
+    }
+
+    @Override
+    protected String visitSubqueryExpression(SubqueryExpression node, Void 
context) {
+      return "(" + formatSql(node.getQuery()) + ")";
+    }
+
+    @Override
+    protected String visitExists(ExistsPredicate node, Void context) {
+      return "(EXISTS " + formatSql(node.getSubquery()) + ")";
+    }
+
+    @Override
+    protected String visitIdentifier(Identifier node, Void context) {
+      if (node.isDelimited() || reserved(node.getValue())) {
+        return '"' + node.getValue().replace("\"", "\"\"") + '"';
+      }
+      return node.getValue();
+    }
+
+    @Override
+    protected String visitSymbolReference(SymbolReference node, Void context) {
+      if (symbolReferenceFormatter.isPresent()) {
+        return symbolReferenceFormatter.get().apply(node);
+      }
+      return formatIdentifier(node.getName());
+    }
+
+    private String formatIdentifier(String s) {
+      return '"' + s.replace("\"", "\"\"") + '"';
+    }
+
+    @Override
+    protected String visitDereferenceExpression(DereferenceExpression node, 
Void context) {
+      String baseString = process(node.getBase(), context);
+      return baseString + "." + node.getField().map(this::process).orElse("*");
+    }
+
+    @Override
+    public String visitFieldReference(FieldReference node, Void context) {
+      // add colon so this won't parse
+      return ":input(" + node.getFieldIndex() + ")";
+    }
+
+    @Override
+    protected String visitFunctionCall(FunctionCall node, Void context) {
+      if (QualifiedName.of("LISTAGG").equals(node.getName())) {
+        return visitListagg(node);
+      }
+
+      StringBuilder builder = new StringBuilder();
+
+      String arguments = joinExpressions(node.getArguments());
+      if (node.getArguments().isEmpty() && 
"count".equalsIgnoreCase(node.getName().getSuffix())) {
+        arguments = "*";
+      }
+      if (node.isDistinct()) {
+        arguments = "DISTINCT " + arguments;
+      }
+
+      builder.append(formatName(node.getName())).append('(').append(arguments);
+
+      builder.append(')');
+
+      return builder.toString();
+    }
+
+    @Override
+    protected String visitLogicalExpression(LogicalExpression node, Void 
context) {
+      return "("
+          + node.getTerms().stream()
+              .map(term -> process(term, context))
+              .collect(joining(" " + node.getOperator().toString() + " "))
+          + ")";
+    }
+
+    @Override
+    protected String visitNotExpression(NotExpression node, Void context) {
+      return "(NOT " + process(node.getValue(), context) + ")";
+    }
+
+    @Override
+    protected String visitComparisonExpression(ComparisonExpression node, Void 
context) {
+      return formatBinaryExpression(node.getOperator().getValue(), 
node.getLeft(), node.getRight());
+    }
+
+    @Override
+    protected String visitIsNullPredicate(IsNullPredicate node, Void context) {
+      return "(" + process(node.getValue(), context) + " IS NULL)";
+    }
+
+    @Override
+    protected String visitIsNotNullPredicate(IsNotNullPredicate node, Void 
context) {
+      return "(" + process(node.getValue(), context) + " IS NOT NULL)";
+    }
+
+    @Override
+    protected String visitNullIfExpression(NullIfExpression node, Void 
context) {
+      return "NULLIF("
+          + process(node.getFirst(), context)
+          + ", "
+          + process(node.getSecond(), context)
+          + ')';
+    }
+
+    @Override
+    protected String visitIfExpression(IfExpression node, Void context) {
+      StringBuilder builder = new StringBuilder();
+      builder
+          .append("IF(")
+          .append(process(node.getCondition(), context))
+          .append(", ")
+          .append(process(node.getTrueValue(), context));
+      node.getFalseValue()
+          .map(expression -> builder.append(", ").append(process(expression, 
context)));
+      builder.append(")");
+      return builder.toString();
+    }
+
+    @Override
+    protected String visitCoalesceExpression(CoalesceExpression node, Void 
context) {
+      return "COALESCE(" + joinExpressions(node.getOperands()) + ")";
+    }
+
+    @Override
+    protected String visitArithmeticUnary(ArithmeticUnaryExpression node, Void 
context) {
+      String value = process(node.getValue(), context);
+
+      switch (node.getSign()) {
+          // Unary is ambiguous with respect to negative numbers. "-1" parses 
as a number, but
+          // "-(1)" parses as "unaryMinus(number)"
+          // The parentheses are needed to ensure the parsing roundtrips 
properly.
+        case MINUS:
+          return "-(" + value + ")";
+        case PLUS:
+          return "+" + value;
+        default:
+          throw new IllegalArgumentException("Unknown sign: " + 
node.getSign());
+      }
+    }
+
+    @Override
+    protected String visitArithmeticBinary(ArithmeticBinaryExpression node, 
Void context) {
+      return formatBinaryExpression(node.getOperator().getValue(), 
node.getLeft(), node.getRight());
+    }
+
+    @Override
+    protected String visitLikePredicate(LikePredicate node, Void context) {
+      StringBuilder builder = new StringBuilder();
+
+      builder
+          .append('(')
+          .append(process(node.getValue(), context))
+          .append(" LIKE ")
+          .append(process(node.getPattern(), context));
+
+      node.getEscape()
+          .ifPresent(escape -> builder.append(" ESCAPE 
").append(process(escape, context)));
+
+      builder.append(')');
+
+      return builder.toString();
+    }
+
+    @Override
+    protected String visitAllColumns(AllColumns node, Void context) {
+      StringBuilder builder = new StringBuilder();
+      if (node.getTarget().isPresent()) {
+        builder.append(process(node.getTarget().get(), context));
+        builder.append(".*");
+      } else {
+        builder.append("*");
+      }
+
+      if (!node.getAliases().isEmpty()) {
+        builder.append(" AS (");
+        Joiner.on(", ")
+            .appendTo(
+                builder,
+                node.getAliases().stream().map(alias -> process(alias, 
context)).collect(toList()));
+        builder.append(")");
+      }
+
+      return builder.toString();
+    }
+
+    @Override
+    public String visitCast(Cast node, Void context) {
+      return (node.isSafe() ? "TRY_CAST" : "CAST")
+          + "("
+          + process(node.getExpression(), context)
+          + " AS "
+          + process(node.getType(), context)
+          + ")";
+    }
+
+    @Override
+    protected String visitSearchedCaseExpression(SearchedCaseExpression node, 
Void context) {
+      ImmutableList.Builder<String> parts = ImmutableList.builder();
+      parts.add("CASE");
+      for (WhenClause whenClause : node.getWhenClauses()) {
+        parts.add(process(whenClause, context));
+      }
+
+      node.getDefaultValue().ifPresent(value -> 
parts.add("ELSE").add(process(value, context)));
+
+      parts.add("END");
+
+      return "(" + Joiner.on(' ').join(parts.build()) + ")";
+    }
+
+    @Override
+    protected String visitSimpleCaseExpression(SimpleCaseExpression node, Void 
context) {
+      ImmutableList.Builder<String> parts = ImmutableList.builder();
+
+      parts.add("CASE").add(process(node.getOperand(), context));
+
+      for (WhenClause whenClause : node.getWhenClauses()) {
+        parts.add(process(whenClause, context));
+      }
+
+      node.getDefaultValue().ifPresent(value -> 
parts.add("ELSE").add(process(value, context)));
+
+      parts.add("END");
+
+      return "(" + Joiner.on(' ').join(parts.build()) + ")";
+    }
+
+    @Override
+    protected String visitWhenClause(WhenClause node, Void context) {
+      return "WHEN "
+          + process(node.getOperand(), context)
+          + " THEN "
+          + process(node.getResult(), context);
+    }
+
+    @Override
+    protected String visitBetweenPredicate(BetweenPredicate node, Void 
context) {
+      return "("
+          + process(node.getValue(), context)
+          + " BETWEEN "
+          + process(node.getMin(), context)
+          + " AND "
+          + process(node.getMax(), context)
+          + ")";
+    }
+
+    @Override
+    protected String visitInPredicate(InPredicate node, Void context) {
+      return "("
+          + process(node.getValue(), context)
+          + " IN "
+          + process(node.getValueList(), context)
+          + ")";
+    }
+
+    @Override
+    protected String visitInListExpression(InListExpression node, Void 
context) {
+      return "(" + joinExpressions(node.getValues()) + ")";
+    }
+
+    @Override
+    protected String visitQuantifiedComparisonExpression(
+        QuantifiedComparisonExpression node, Void context) {
+      return String.format(
+          "(%s %s %s %s)",
+          process(node.getValue(), context),
+          node.getOperator().getValue(),
+          node.getQuantifier(),
+          process(node.getSubquery(), context));
+    }
+
+    @Override
+    protected String visitGenericDataType(GenericDataType node, Void context) {
+      StringBuilder result = new StringBuilder();
+      result.append(node.getName());
+
+      if (!node.getArguments().isEmpty()) {
+        result.append(
+            node.getArguments().stream().map(this::process).collect(joining(", 
", "(", ")")));
+      }
+
+      return result.toString();
+    }
+
+    @Override
+    protected String visitTypeParameter(TypeParameter node, Void context) {
+      return process(node.getValue(), context);
+    }
+
+    @Override
+    protected String visitNumericTypeParameter(NumericParameter node, Void 
context) {
+      return node.getValue();
+    }
+
+    private String formatBinaryExpression(String operator, Expression left, 
Expression right) {
+      return '(' + process(left, null) + ' ' + operator + ' ' + process(right, 
null) + ')';
+    }
+
+    private String joinExpressions(List<Expression> expressions) {
+      return expressions.stream().map(e -> process(e, 
null)).collect(joining(", "));
+    }
+
+    /**
+     * Returns the formatted `LISTAGG` function call corresponding to the 
specified node.
+     *
+     * <p>During the parsing of the syntax tree, the `LISTAGG` expression is 
synthetically converted
+     * to a function call. This method formats the specified {@link 
FunctionCall} node to correspond
+     * to the standardised syntax of the `LISTAGG` expression.
+     *
+     * @param node the `LISTAGG` function call
+     */
+    private String visitListagg(FunctionCall node) {
+      StringBuilder builder = new StringBuilder();
+
+      List<Expression> arguments = node.getArguments();
+      Expression expression = arguments.get(0);
+      Expression separator = arguments.get(1);
+      BooleanLiteral overflowError = (BooleanLiteral) arguments.get(2);
+      Expression overflowFiller = arguments.get(3);
+      BooleanLiteral showOverflowEntryCount = (BooleanLiteral) 
arguments.get(4);
+
+      String innerArguments = joinExpressions(ImmutableList.of(expression, 
separator));
+      if (node.isDistinct()) {
+        innerArguments = "DISTINCT " + innerArguments;
+      }
+
+      builder.append("LISTAGG").append('(').append(innerArguments);
+
+      builder.append(" ON OVERFLOW ");
+      if (overflowError.getValue()) {
+        builder.append(" ERROR");
+      } else {
+        builder.append(" TRUNCATE").append(' ').append(process(overflowFiller, 
null));
+        if (showOverflowEntryCount.getValue()) {
+          builder.append(" WITH COUNT");
+        } else {
+          builder.append(" WITHOUT COUNT");
+        }
+      }
+
+      builder.append(')');
+
+      return builder.toString();
+    }
+  }
+
+  static String formatStringLiteral(String s) {
+    return "'" + s.replace("'", "''") + "'";
+  }
+
+  public static String formatOrderBy(OrderBy orderBy) {
+    return "ORDER BY " + formatSortItems(orderBy.getSortItems());
+  }
+
+  public static String formatSortItems(List<SortItem> sortItems) {
+    return 
sortItems.stream().map(sortItemFormatterFunction()).collect(joining(", "));
+  }
+
+  static String formatGroupBy(List<GroupingElement> groupingElements) {
+    return groupingElements.stream()
+        .map(
+            groupingElement -> {
+              String result = "";
+              if (groupingElement instanceof SimpleGroupBy) {
+                List<Expression> columns = groupingElement.getExpressions();
+                if (columns.size() == 1) {
+                  result = formatExpression(getOnlyElement(columns));
+                } else {
+                  result = formatGroupingSet(columns);
+                }
+              } else if (groupingElement instanceof GroupingSets) {
+                GroupingSets groupingSets = (GroupingSets) groupingElement;
+                String type = null;
+                switch (groupingSets.getType()) {
+                  case EXPLICIT:
+                    type = "GROUPING SETS";
+                    break;
+                  case CUBE:
+                    type = "CUBE";
+                    break;
+                  case ROLLUP:
+                    type = "ROLLUP";
+                    break;
+                }
+
+                result =
+                    groupingSets.getSets().stream()
+                        .map(ExpressionFormatter::formatGroupingSet)
+                        .collect(joining(", ", type + " (", ")"));
+              }
+              return result;
+            })
+        .collect(joining(", "));
+  }
+
+  private static boolean isAsciiPrintable(int codePoint) {
+    return codePoint >= 0x20 && codePoint < 0x7F;
+  }
+
+  private static String formatGroupingSet(List<Expression> groupingSet) {
+    return groupingSet.stream()
+        .map(ExpressionFormatter::formatExpression)
+        .collect(joining(", ", "(", ")"));
+  }
+
+  private static Function<SortItem, String> sortItemFormatterFunction() {
+    return input -> {
+      StringBuilder builder = new StringBuilder();
+
+      builder.append(formatExpression(input.getSortKey()));
+
+      switch (input.getOrdering()) {
+        case ASCENDING:
+          builder.append(" ASC");
+          break;
+        case DESCENDING:
+          builder.append(" DESC");
+          break;
+      }
+
+      switch (input.getNullOrdering()) {
+        case FIRST:
+          builder.append(" NULLS FIRST");
+          break;
+        case LAST:
+          builder.append(" NULLS LAST");
+          break;
+      }
+
+      return builder.toString();
+    };
   }
 }
diff --git 
a/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/util/SqlFormatter.java
 
b/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/util/SqlFormatter.java
new file mode 100644
index 00000000000..ed430724411
--- /dev/null
+++ 
b/iotdb-core/relational-parser/src/main/java/org/apache/iotdb/db/relational/sql/util/SqlFormatter.java
@@ -0,0 +1,783 @@
+/*
+ * 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.iotdb.db.relational.sql.util;
+
+import org.apache.iotdb.db.relational.sql.tree.AddColumn;
+import org.apache.iotdb.db.relational.sql.tree.AliasedRelation;
+import org.apache.iotdb.db.relational.sql.tree.AllColumns;
+import org.apache.iotdb.db.relational.sql.tree.AstVisitor;
+import org.apache.iotdb.db.relational.sql.tree.ColumnDefinition;
+import org.apache.iotdb.db.relational.sql.tree.CreateDB;
+import org.apache.iotdb.db.relational.sql.tree.CreateFunction;
+import org.apache.iotdb.db.relational.sql.tree.CreateTable;
+import org.apache.iotdb.db.relational.sql.tree.Delete;
+import org.apache.iotdb.db.relational.sql.tree.DropColumn;
+import org.apache.iotdb.db.relational.sql.tree.DropDB;
+import org.apache.iotdb.db.relational.sql.tree.DropFunction;
+import org.apache.iotdb.db.relational.sql.tree.DropTable;
+import org.apache.iotdb.db.relational.sql.tree.Except;
+import org.apache.iotdb.db.relational.sql.tree.Explain;
+import org.apache.iotdb.db.relational.sql.tree.ExplainAnalyze;
+import org.apache.iotdb.db.relational.sql.tree.Expression;
+import org.apache.iotdb.db.relational.sql.tree.Identifier;
+import org.apache.iotdb.db.relational.sql.tree.Insert;
+import org.apache.iotdb.db.relational.sql.tree.Intersect;
+import org.apache.iotdb.db.relational.sql.tree.Join;
+import org.apache.iotdb.db.relational.sql.tree.JoinCriteria;
+import org.apache.iotdb.db.relational.sql.tree.JoinOn;
+import org.apache.iotdb.db.relational.sql.tree.JoinUsing;
+import org.apache.iotdb.db.relational.sql.tree.Limit;
+import org.apache.iotdb.db.relational.sql.tree.NaturalJoin;
+import org.apache.iotdb.db.relational.sql.tree.Node;
+import org.apache.iotdb.db.relational.sql.tree.Offset;
+import org.apache.iotdb.db.relational.sql.tree.OrderBy;
+import org.apache.iotdb.db.relational.sql.tree.Property;
+import org.apache.iotdb.db.relational.sql.tree.QualifiedName;
+import org.apache.iotdb.db.relational.sql.tree.Query;
+import org.apache.iotdb.db.relational.sql.tree.QuerySpecification;
+import org.apache.iotdb.db.relational.sql.tree.Relation;
+import org.apache.iotdb.db.relational.sql.tree.RenameColumn;
+import org.apache.iotdb.db.relational.sql.tree.RenameTable;
+import org.apache.iotdb.db.relational.sql.tree.Row;
+import org.apache.iotdb.db.relational.sql.tree.Select;
+import org.apache.iotdb.db.relational.sql.tree.SelectItem;
+import org.apache.iotdb.db.relational.sql.tree.SetProperties;
+import org.apache.iotdb.db.relational.sql.tree.ShowDB;
+import org.apache.iotdb.db.relational.sql.tree.ShowFunctions;
+import org.apache.iotdb.db.relational.sql.tree.ShowTables;
+import org.apache.iotdb.db.relational.sql.tree.SingleColumn;
+import org.apache.iotdb.db.relational.sql.tree.Table;
+import org.apache.iotdb.db.relational.sql.tree.TableSubquery;
+import org.apache.iotdb.db.relational.sql.tree.Union;
+import org.apache.iotdb.db.relational.sql.tree.Update;
+import org.apache.iotdb.db.relational.sql.tree.UpdateAssignment;
+import org.apache.iotdb.db.relational.sql.tree.Values;
+import org.apache.iotdb.db.relational.sql.tree.WithQuery;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Strings;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Optional;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.collect.ImmutableList.toImmutableList;
+import static com.google.common.collect.Iterables.getOnlyElement;
+import static java.util.Objects.requireNonNull;
+import static java.util.stream.Collectors.joining;
+import static 
org.apache.iotdb.db.relational.sql.util.ExpressionFormatter.formatGroupBy;
+import static 
org.apache.iotdb.db.relational.sql.util.ExpressionFormatter.formatOrderBy;
+
+public final class SqlFormatter {
+
+  private static final String INDENT = "   ";
+
+  private SqlFormatter() {}
+
+  public static String formatSql(Node root) {
+    StringBuilder builder = new StringBuilder();
+    new Formatter(builder).process(root, 0);
+    return builder.toString();
+  }
+
+  private static String formatName(Identifier identifier) {
+    return ExpressionFormatter.formatExpression(identifier);
+  }
+
+  public static String formatName(QualifiedName name) {
+    return 
name.getOriginalParts().stream().map(SqlFormatter::formatName).collect(joining("."));
+  }
+
+  private static String formatExpression(Expression expression) {
+    return ExpressionFormatter.formatExpression(expression);
+  }
+
+  private static class Formatter extends AstVisitor<Void, Integer> {
+    private static class SqlBuilder {
+      private final StringBuilder builder;
+
+      public SqlBuilder(StringBuilder builder) {
+        this.builder = requireNonNull(builder, "builder is null");
+      }
+
+      @CanIgnoreReturnValue
+      public SqlBuilder append(CharSequence value) {
+        builder.append(value);
+        return this;
+      }
+
+      @CanIgnoreReturnValue
+      public SqlBuilder append(char c) {
+        builder.append(c);
+        return this;
+      }
+    }
+
+    private final SqlBuilder builder;
+
+    public Formatter(StringBuilder builder) {
+      this.builder = new SqlBuilder(builder);
+    }
+
+    @Override
+    protected Void visitNode(Node node, Integer indent) {
+      throw new UnsupportedOperationException("not yet implemented: " + node);
+    }
+
+    @Override
+    protected Void visitExpression(Expression node, Integer indent) {
+      checkArgument(indent == 0, "visitExpression should only be called at 
root");
+      builder.append(formatExpression(node));
+      return null;
+    }
+
+    @Override
+    protected Void visitQuery(Query node, Integer indent) {
+
+      node.getWith()
+          .ifPresent(
+              with -> {
+                append(indent, "WITH");
+                if (with.isRecursive()) {
+                  builder.append(" RECURSIVE");
+                }
+                builder.append("\n  ");
+                Iterator<WithQuery> queries = with.getQueries().iterator();
+                while (queries.hasNext()) {
+                  WithQuery query = queries.next();
+                  append(indent, formatName(query.getName()));
+                  query
+                      .getColumnNames()
+                      .ifPresent(columnNames -> appendAliasColumns(builder, 
columnNames));
+                  builder.append(" AS ");
+                  process(new TableSubquery(query.getQuery()), indent);
+                  builder.append('\n');
+                  if (queries.hasNext()) {
+                    builder.append(", ");
+                  }
+                }
+              });
+
+      processRelation(node.getQueryBody(), indent);
+      node.getOrderBy().ifPresent(orderBy -> process(orderBy, indent));
+      node.getOffset().ifPresent(offset -> process(offset, indent));
+      node.getLimit().ifPresent(limit -> process(limit, indent));
+      return null;
+    }
+
+    @Override
+    protected Void visitQuerySpecification(QuerySpecification node, Integer 
indent) {
+      process(node.getSelect(), indent);
+
+      node.getFrom()
+          .ifPresent(
+              from -> {
+                append(indent, "FROM");
+                builder.append('\n');
+                append(indent, "  ");
+                process(from, indent);
+              });
+
+      builder.append('\n');
+
+      node.getWhere()
+          .ifPresent(where -> append(indent, "WHERE " + 
formatExpression(where)).append('\n'));
+
+      node.getGroupBy()
+          .ifPresent(
+              groupBy ->
+                  append(
+                          indent,
+                          "GROUP BY "
+                              + (groupBy.isDistinct() ? " DISTINCT " : "")
+                              + formatGroupBy(groupBy.getGroupingElements()))
+                      .append('\n'));
+
+      node.getHaving()
+          .ifPresent(having -> append(indent, "HAVING " + 
formatExpression(having)).append('\n'));
+
+      node.getOrderBy().ifPresent(orderBy -> process(orderBy, indent));
+      node.getOffset().ifPresent(offset -> process(offset, indent));
+      node.getLimit().ifPresent(limit -> process(limit, indent));
+      return null;
+    }
+
+    @Override
+    protected Void visitOrderBy(OrderBy node, Integer indent) {
+      append(indent, formatOrderBy(node)).append('\n');
+      return null;
+    }
+
+    @Override
+    protected Void visitOffset(Offset node, Integer indent) {
+      append(indent, "OFFSET 
").append(formatExpression(node.getRowCount())).append(" ROWS\n");
+      return null;
+    }
+
+    @Override
+    protected Void visitLimit(Limit node, Integer indent) {
+      append(indent, "LIMIT 
").append(formatExpression(node.getRowCount())).append('\n');
+      return null;
+    }
+
+    @Override
+    protected Void visitSelect(Select node, Integer indent) {
+      append(indent, "SELECT");
+      if (node.isDistinct()) {
+        builder.append(" DISTINCT");
+      }
+
+      if (node.getSelectItems().size() > 1) {
+        boolean first = true;
+        for (SelectItem item : node.getSelectItems()) {
+          builder.append("\n").append(indentString(indent)).append(first ? "  
" : ", ");
+
+          process(item, indent);
+          first = false;
+        }
+      } else {
+        builder.append(' ');
+        process(getOnlyElement(node.getSelectItems()), indent);
+      }
+
+      builder.append('\n');
+
+      return null;
+    }
+
+    @Override
+    protected Void visitSingleColumn(SingleColumn node, Integer indent) {
+      builder.append(formatExpression(node.getExpression()));
+      node.getAlias().ifPresent(alias -> builder.append(' 
').append(formatName(alias)));
+
+      return null;
+    }
+
+    @Override
+    protected Void visitAllColumns(AllColumns node, Integer indent) {
+      node.getTarget().ifPresent(value -> 
builder.append(formatExpression(value)).append("."));
+      builder.append("*");
+
+      if (!node.getAliases().isEmpty()) {
+        builder
+            .append(" AS (")
+            .append(
+                Joiner.on(", ")
+                    .join(
+                        node.getAliases().stream()
+                            .map(SqlFormatter::formatName)
+                            .collect(toImmutableList())))
+            .append(")");
+      }
+
+      return null;
+    }
+
+    @Override
+    protected Void visitTable(Table node, Integer indent) {
+      builder.append(formatName(node.getName()));
+      return null;
+    }
+
+    @Override
+    protected Void visitJoin(Join node, Integer indent) {
+      JoinCriteria criteria = node.getCriteria().orElse(null);
+      String type = node.getType().toString();
+      if (criteria instanceof NaturalJoin) {
+        type = "NATURAL " + type;
+      }
+
+      if (node.getType() != Join.Type.IMPLICIT) {
+        builder.append('(');
+      }
+      process(node.getLeft(), indent);
+
+      builder.append('\n');
+      if (node.getType() == Join.Type.IMPLICIT) {
+        append(indent, ", ");
+      } else {
+        append(indent, type).append(" JOIN ");
+      }
+
+      process(node.getRight(), indent);
+
+      if (node.getType() != Join.Type.CROSS && node.getType() != 
Join.Type.IMPLICIT) {
+        if (criteria instanceof JoinUsing) {
+          JoinUsing using = (JoinUsing) criteria;
+          builder.append(" USING (").append(Joiner.on(", 
").join(using.getColumns())).append(")");
+        } else if (criteria instanceof JoinOn) {
+          JoinOn on = (JoinOn) criteria;
+          builder.append(" ON ").append(formatExpression(on.getExpression()));
+        } else if (!(criteria instanceof NaturalJoin)) {
+          throw new UnsupportedOperationException("unknown join criteria: " + 
criteria);
+        }
+      }
+
+      if (node.getType() != Join.Type.IMPLICIT) {
+        builder.append(")");
+      }
+
+      return null;
+    }
+
+    @Override
+    protected Void visitAliasedRelation(AliasedRelation node, Integer indent) {
+      builder.append("( ");
+      process(node, indent + 1);
+      append(indent, ")");
+
+      builder.append(' ').append(formatName(node.getAlias()));
+      appendAliasColumns(builder, node.getColumnNames());
+
+      return null;
+    }
+
+    @Override
+    protected Void visitValues(Values node, Integer indent) {
+      builder.append(" VALUES ");
+
+      boolean first = true;
+      for (Expression row : node.getRows()) {
+        builder.append("\n").append(indentString(indent)).append(first ? "  " 
: ", ");
+
+        builder.append(formatExpression(row));
+        first = false;
+      }
+      builder.append('\n');
+
+      return null;
+    }
+
+    @Override
+    protected Void visitTableSubquery(TableSubquery node, Integer indent) {
+      builder.append('(').append('\n');
+
+      process(node.getQuery(), indent + 1);
+
+      append(indent, ") ");
+
+      return null;
+    }
+
+    @Override
+    protected Void visitUnion(Union node, Integer indent) {
+      Iterator<Relation> relations = node.getRelations().iterator();
+
+      while (relations.hasNext()) {
+        processRelation(relations.next(), indent);
+
+        if (relations.hasNext()) {
+          builder.append("UNION ");
+          if (!node.isDistinct()) {
+            builder.append("ALL ");
+          }
+        }
+      }
+
+      return null;
+    }
+
+    @Override
+    protected Void visitExcept(Except node, Integer indent) {
+      processRelation(node.getLeft(), indent);
+
+      builder.append("EXCEPT ");
+      if (!node.isDistinct()) {
+        builder.append("ALL ");
+      }
+
+      processRelation(node.getRight(), indent);
+
+      return null;
+    }
+
+    @Override
+    protected Void visitIntersect(Intersect node, Integer indent) {
+      Iterator<Relation> relations = node.getRelations().iterator();
+
+      while (relations.hasNext()) {
+        processRelation(relations.next(), indent);
+
+        if (relations.hasNext()) {
+          builder.append("INTERSECT ");
+          if (!node.isDistinct()) {
+            builder.append("ALL ");
+          }
+        }
+      }
+
+      return null;
+    }
+
+    @Override
+    protected Void visitExplain(Explain node, Integer indent) {
+      builder.append("EXPLAIN ");
+
+      builder.append("\n");
+
+      process(node.getStatement(), indent);
+
+      return null;
+    }
+
+    @Override
+    protected Void visitExplainAnalyze(ExplainAnalyze node, Integer indent) {
+      builder.append("EXPLAIN ANALYZE");
+      if (node.isVerbose()) {
+        builder.append(" VERBOSE");
+      }
+      builder.append("\n");
+
+      process(node.getStatement(), indent);
+
+      return null;
+    }
+
+    @Override
+    protected Void visitShowDB(ShowDB node, Integer indent) {
+      builder.append("SHOW DATABASE");
+
+      return null;
+    }
+
+    @Override
+    protected Void visitShowTables(ShowTables node, Integer indent) {
+      builder.append("SHOW TABLES");
+
+      node.getDbName().ifPresent(db -> builder.append(" FROM 
").append(formatName(db)));
+
+      return null;
+    }
+
+    @Override
+    protected Void visitShowFunctions(ShowFunctions node, Integer indent) {
+      builder.append("SHOW FUNCTIONS");
+
+      return null;
+    }
+
+    @Override
+    protected Void visitDelete(Delete node, Integer indent) {
+      builder.append("DELETE FROM 
").append(formatName(node.getTable().getName()));
+
+      node.getWhere().ifPresent(where -> builder.append(" WHERE 
").append(formatExpression(where)));
+
+      return null;
+    }
+
+    @Override
+    protected Void visitCreateDB(CreateDB node, Integer indent) {
+      builder.append("CREATE DATABASE ");
+      if (node.isSetIfNotExists()) {
+        builder.append("IF NOT EXISTS ");
+      }
+      builder.append(node.getDbName()).append(" ");
+
+      builder.append(formatPropertiesMultiLine(node.getProperties()));
+
+      return null;
+    }
+
+    @Override
+    protected Void visitDropDB(DropDB node, Integer indent) {
+      builder.append("DROP DATABASE ");
+      if (node.isExists()) {
+        builder.append("IF EXISTS ");
+      }
+      builder.append(formatName(node.getDbName()));
+      return null;
+    }
+
+    @Override
+    protected Void visitCreateTable(CreateTable node, Integer indent) {
+      builder.append("CREATE TABLE ");
+      if (node.isIfNotExists()) {
+        builder.append("IF NOT EXISTS ");
+      }
+      String tableName = formatName(node.getName());
+      builder.append(tableName).append(" (\n");
+
+      String elementIndent = indentString(indent + 1);
+      String columnList =
+          node.getElements().stream()
+              .map(
+                  element -> {
+                    if (element != null) {
+                      return elementIndent + formatColumnDefinition(element);
+                    }
+
+                    throw new UnsupportedOperationException("unknown table 
element: " + element);
+                  })
+              .collect(joining(",\n"));
+      builder.append(columnList);
+      builder.append("\n").append(")");
+
+      builder.append(formatPropertiesMultiLine(node.getProperties()));
+
+      return null;
+    }
+
+    private String formatPropertiesMultiLine(List<Property> properties) {
+      if (properties.isEmpty()) {
+        return "";
+      }
+
+      String propertyList =
+          properties.stream()
+              .map(
+                  element ->
+                      INDENT
+                          + formatName(element.getName())
+                          + " = "
+                          + (element.isSetToDefault()
+                              ? "DEFAULT"
+                              : 
formatExpression(element.getNonDefaultValue())))
+              .collect(joining(",\n"));
+
+      return "\nWITH (\n" + propertyList + "\n)";
+    }
+
+    private String formatPropertiesSingleLine(List<Property> properties) {
+      if (properties.isEmpty()) {
+        return "";
+      }
+
+      return " WITH ( " + joinProperties(properties) + " )";
+    }
+
+    private String formatColumnDefinition(ColumnDefinition column) {
+      StringBuilder stringBuilder =
+          new StringBuilder()
+              .append(formatName(column.getName()))
+              .append(" ")
+              .append(column.getType())
+              .append(" ")
+              .append(column.getColumnCategory());
+
+      column
+          .getCharsetName()
+          .ifPresent(charset -> stringBuilder.append(" CHARSET 
").append(charset));
+      return stringBuilder.toString();
+    }
+
+    @Override
+    protected Void visitDropTable(DropTable node, Integer indent) {
+      builder.append("DROP TABLE ");
+      if (node.isExists()) {
+        builder.append("IF EXISTS ");
+      }
+      builder.append(formatName(node.getTableName()));
+
+      return null;
+    }
+
+    @Override
+    protected Void visitRenameTable(RenameTable node, Integer indent) {
+      builder.append("ALTER TABLE ");
+      builder
+          .append(formatName(node.getSource()))
+          .append(" RENAME TO ")
+          .append(formatName(node.getTarget()));
+
+      return null;
+    }
+
+    @Override
+    protected Void visitSetProperties(SetProperties node, Integer context) {
+      SetProperties.Type type = node.getType();
+      builder.append("ALTER ");
+      switch (type) {
+        case TABLE:
+          builder.append("TABLE ");
+        case MATERIALIZED_VIEW:
+          builder.append("MATERIALIZED VIEW ");
+      }
+
+      builder
+          .append(formatName(node.getName()))
+          .append(" SET PROPERTIES ")
+          .append(joinProperties(node.getProperties()));
+
+      return null;
+    }
+
+    private String joinProperties(List<Property> properties) {
+      return properties.stream()
+          .map(
+              element ->
+                  formatName(element.getName())
+                      + " = "
+                      + (element.isSetToDefault()
+                          ? "DEFAULT"
+                          : formatExpression(element.getNonDefaultValue())))
+          .collect(joining(", "));
+    }
+
+    @Override
+    protected Void visitRenameColumn(RenameColumn node, Integer indent) {
+      builder.append("ALTER TABLE ");
+      builder.append(formatName(node.getTable())).append(" RENAME COLUMN ");
+      builder
+          .append(formatName(node.getSource()))
+          .append(" TO ")
+          .append(formatName(node.getTarget()));
+
+      return null;
+    }
+
+    @Override
+    protected Void visitDropColumn(DropColumn node, Integer indent) {
+      builder.append("ALTER TABLE ");
+      builder.append(formatName(node.getTable())).append(" DROP COLUMN ");
+      builder.append(formatName(node.getField()));
+
+      return null;
+    }
+
+    @Override
+    protected Void visitAddColumn(AddColumn node, Integer indent) {
+      builder.append("ALTER TABLE ");
+
+      builder.append(formatName(node.getTableName())).append(" ADD COLUMN ");
+      builder.append(formatColumnDefinition(node.getColumn()));
+
+      return null;
+    }
+
+    @Override
+    protected Void visitInsert(Insert node, Integer indent) {
+      builder.append("INSERT INTO ").append(formatName(node.getTarget()));
+
+      node.getColumns()
+          .ifPresent(
+              columns -> builder.append(" (").append(Joiner.on(", 
").join(columns)).append(")"));
+
+      builder.append("\n");
+
+      process(node.getQuery(), indent);
+
+      return null;
+    }
+
+    @Override
+    protected Void visitUpdate(Update node, Integer indent) {
+      builder.append("UPDATE 
").append(formatName(node.getTable().getName())).append(" SET");
+      int setCounter = node.getAssignments().size() - 1;
+      for (UpdateAssignment assignment : node.getAssignments()) {
+        builder
+            .append("\n")
+            .append(indentString(indent + 1))
+            .append(assignment.getName().getValue())
+            .append(" = ")
+            .append(formatExpression(assignment.getValue()));
+        if (setCounter > 0) {
+          builder.append(",");
+        }
+        setCounter--;
+      }
+      node.getWhere()
+          .ifPresent(
+              where ->
+                  builder
+                      .append("\n")
+                      .append(indentString(indent))
+                      .append("WHERE ")
+                      .append(formatExpression(where)));
+      return null;
+    }
+
+    @Override
+    protected Void visitRow(Row node, Integer indent) {
+      builder.append("ROW(");
+      boolean firstItem = true;
+      for (Expression item : node.getItems()) {
+        if (!firstItem) {
+          builder.append(", ");
+        }
+        process(item, indent);
+        firstItem = false;
+      }
+      builder.append(")");
+      return null;
+    }
+
+    @Override
+    protected Void visitCreateFunction(CreateFunction node, Integer indent) {
+      builder
+          .append("CREATE FUNCTION ")
+          .append(node.getUdfName())
+          .append(" AS ")
+          .append(node.getClassName());
+      node.getUriString().ifPresent(uri -> builder.append(" USING URI 
").append(uri));
+      return null;
+    }
+
+    @Override
+    protected Void visitDropFunction(DropFunction node, Integer indent) {
+      builder.append("DROP FUNCTION ");
+      builder.append(node.getUdfName());
+      return null;
+    }
+
+    private void appendBeginLabel(Optional<Identifier> label) {
+      label.ifPresent(value -> builder.append(formatName(value)).append(": "));
+    }
+
+    private void processRelation(Relation relation, Integer indent) {
+      // TODO: handle this properly
+      if (relation instanceof Table) {
+        builder.append("TABLE ").append(formatName(((Table) 
relation).getName())).append('\n');
+      } else {
+        process(relation, indent);
+      }
+    }
+
+    private SqlBuilder append(int indent, String value) {
+      return builder.append(indentString(indent)).append(value);
+    }
+
+    private static String indentString(int indent) {
+      return Strings.repeat(INDENT, indent);
+    }
+
+    private void formatDefinitionList(List<String> elements, int indent) {
+      if (elements.size() == 1) {
+        builder.append(" ").append(getOnlyElement(elements)).append("\n");
+      } else {
+        builder.append("\n");
+        for (int i = 0; i < elements.size() - 1; i++) {
+          append(indent, elements.get(i)).append(",\n");
+        }
+        append(indent, elements.get(elements.size() - 1)).append("\n");
+      }
+    }
+
+    private void appendAliasColumns(Formatter.SqlBuilder builder, 
List<Identifier> columns) {
+      if ((columns != null) && !columns.isEmpty()) {
+        String formattedColumns =
+            columns.stream().map(SqlFormatter::formatName).collect(joining(", 
"));
+
+        builder.append(" (").append(formattedColumns).append(')');
+      }
+    }
+  }
+}

Reply via email to