You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by jh...@apache.org on 2020/01/06 08:12:05 UTC
[calcite] 07/07: [CALCITE-3526] SqlPrettyWriter should have options
to fold/chop long lines, print leading commas
This is an automated email from the ASF dual-hosted git repository.
jhyde pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/calcite.git
commit c416c31fc376868bdd672afd84ec06dc75d56575
Author: Julian Hyde <jh...@apache.org>
AuthorDate: Mon Oct 28 13:02:01 2019 -0700
[CALCITE-3526] SqlPrettyWriter should have options to fold/chop long lines, print leading commas
Move SqlPrettyWriter.Config to top-level class SqlWriterConfig, and
make it an interface implemented via ImmutableBeans; fixes
[CALCITE-1585] SqlPrettyWriter doesn't respect alwaysUseParentheses
because config is now held in an immutable object rather than the
SqlPrettyWriter.
Add option SqlWriterConfig.withLeadingComma, to generate
SELECT x
, y
FROM t
rather than
SELECT x,
y
FROM t
Add option SqlWriterConfig.fromLength, which controls when lines are
considered long enough to chop or fold. Unlike lineLength(), which is
absolute, it is relative to the current sub-query.
Refactor: add SqlNodeList.SINGLETON_STAR.
Add lots of options including selectFolding(), fromFolding(),
clauseEndsList().
Extend SqlParserTest to test randomly-configured SqlPrettyWriter.
---
.../org/apache/calcite/test/BabelQuidemTest.java | 4 +-
babel/src/test/resources/sql/select.iq | 2 +-
.../calcite/adapter/cassandra/CassandraSchema.java | 13 +-
.../org/apache/calcite/adapter/jdbc/JdbcTable.java | 12 +-
.../calcite/rel/rel2sql/RelToSqlConverter.java | 3 +-
.../java/org/apache/calcite/sql/SqlAsOperator.java | 2 +-
.../main/java/org/apache/calcite/sql/SqlJoin.java | 20 +-
.../org/apache/calcite/sql/SqlMatchRecognize.java | 6 +-
.../main/java/org/apache/calcite/sql/SqlNode.java | 44 +-
.../java/org/apache/calcite/sql/SqlNodeList.java | 57 +-
.../java/org/apache/calcite/sql/SqlOperator.java | 36 +-
.../java/org/apache/calcite/sql/SqlOrderBy.java | 6 +-
.../org/apache/calcite/sql/SqlSelectOperator.java | 65 +-
.../apache/calcite/sql/SqlWithinGroupOperator.java | 2 +-
.../java/org/apache/calcite/sql/SqlWriter.java | 55 +-
.../org/apache/calcite/sql/SqlWriterConfig.java | 434 +++++++
.../apache/calcite/sql/fun/SqlCaseOperator.java | 5 +-
.../calcite/sql/fun/SqlStdOperatorTable.java | 1 +
.../apache/calcite/sql/pretty/SqlPrettyWriter.java | 1231 ++++++++++++--------
.../apache/calcite/sql/type/IntervalSqlType.java | 11 +-
.../apache/calcite/sql/parser/SqlParserTest.java | 168 ++-
.../calcite/sql/test/SqlOperatorBaseTest.java | 4 +-
.../calcite/sql/test/SqlPrettyWriterTest.java | 262 ++++-
.../org/apache/calcite/test/JdbcAdapterTest.java | 2 +-
.../calcite/sql/test/SqlPrettyWriterTest.xml | 337 +++++-
.../org/apache/calcite/piglet/PigConverter.java | 13 +-
.../java/org/apache/calcite/test/PigRelOpTest.java | 122 +-
.../org/apache/calcite/sql/ddl/SqlDdlNodes.java | 22 +-
28 files changed, 2106 insertions(+), 833 deletions(-)
diff --git a/babel/src/test/java/org/apache/calcite/test/BabelQuidemTest.java b/babel/src/test/java/org/apache/calcite/test/BabelQuidemTest.java
index bc82d34..51477db 100644
--- a/babel/src/test/java/org/apache/calcite/test/BabelQuidemTest.java
+++ b/babel/src/test/java/org/apache/calcite/test/BabelQuidemTest.java
@@ -23,7 +23,6 @@ import org.apache.calcite.plan.Contexts;
import org.apache.calcite.schema.SchemaPlus;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.SqlWriter;
-import org.apache.calcite.sql.dialect.CalciteSqlDialect;
import org.apache.calcite.sql.parser.SqlParser;
import org.apache.calcite.sql.parser.babel.SqlBabelParserImpl;
import org.apache.calcite.sql.pretty.SqlPrettyWriter;
@@ -152,8 +151,7 @@ public class BabelQuidemTest extends QuidemTest {
final Planner planner = Frameworks.getPlanner(config.build());
final SqlNode node = planner.parse(sqlCommand.sql);
final SqlNode validateNode = planner.validate(node);
- final SqlWriter sqlWriter =
- new SqlPrettyWriter(CalciteSqlDialect.DEFAULT);
+ final SqlWriter sqlWriter = new SqlPrettyWriter();
validateNode.unparse(sqlWriter, 0, 0);
x.echo(ImmutableList.of(sqlWriter.toSqlString().getSql()));
} else {
diff --git a/babel/src/test/resources/sql/select.iq b/babel/src/test/resources/sql/select.iq
index 4e9f822..07399eb 100755
--- a/babel/src/test/resources/sql/select.iq
+++ b/babel/src/test/resources/sql/select.iq
@@ -25,7 +25,7 @@ ORDER BY emp.deptno;
SELECT "EMP"."ENAME"
FROM "scott"."EMP" AS "EMP",
- "scott"."DEPT" AS "DEPT"
+ "scott"."DEPT" AS "DEPT"
ORDER BY "EMP"."DEPTNO"
!explain-validated-on all
diff --git a/cassandra/src/main/java/org/apache/calcite/adapter/cassandra/CassandraSchema.java b/cassandra/src/main/java/org/apache/calcite/adapter/cassandra/CassandraSchema.java
index fd6819c..85c4c6d 100644
--- a/cassandra/src/main/java/org/apache/calcite/adapter/cassandra/CassandraSchema.java
+++ b/cassandra/src/main/java/org/apache/calcite/adapter/cassandra/CassandraSchema.java
@@ -30,7 +30,7 @@ import org.apache.calcite.schema.impl.AbstractSchema;
import org.apache.calcite.schema.impl.MaterializedViewTable;
import org.apache.calcite.sql.SqlSelect;
import org.apache.calcite.sql.SqlWriter;
-import org.apache.calcite.sql.dialect.CalciteSqlDialect;
+import org.apache.calcite.sql.SqlWriterConfig;
import org.apache.calcite.sql.parser.SqlParseException;
import org.apache.calcite.sql.parser.SqlParser;
import org.apache.calcite.sql.pretty.SqlPrettyWriter;
@@ -54,8 +54,6 @@ import com.google.common.collect.ImmutableMap;
import org.slf4j.Logger;
-import java.io.PrintWriter;
-import java.io.StringWriter;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
@@ -290,11 +288,12 @@ public class CassandraSchema extends AbstractSchema {
continue;
}
- StringWriter stringWriter = new StringWriter(query.length());
- PrintWriter printWriter = new PrintWriter(stringWriter);
- SqlWriter writer = new SqlPrettyWriter(CalciteSqlDialect.DEFAULT, true, printWriter);
+ final StringBuilder buf = new StringBuilder(query.length());
+ final SqlWriterConfig config = SqlPrettyWriter.config()
+ .withAlwaysUseParentheses(true);
+ final SqlWriter writer = new SqlPrettyWriter(config, buf);
parsedQuery.unparse(writer, 0, 0);
- query = stringWriter.toString();
+ query = buf.toString();
// Add the view for this query
String viewName = "$" + getTableNames().size();
diff --git a/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcTable.java b/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcTable.java
index b1f32ad..3a8eff5 100644
--- a/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcTable.java
+++ b/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcTable.java
@@ -47,6 +47,7 @@ import org.apache.calcite.schema.impl.AbstractTableQueryable;
import org.apache.calcite.sql.SqlIdentifier;
import org.apache.calcite.sql.SqlNodeList;
import org.apache.calcite.sql.SqlSelect;
+import org.apache.calcite.sql.SqlWriterConfig;
import org.apache.calcite.sql.parser.SqlParserPos;
import org.apache.calcite.sql.pretty.SqlPrettyWriter;
import org.apache.calcite.sql.util.SqlString;
@@ -58,7 +59,6 @@ import com.google.common.collect.Lists;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.Collections;
import java.util.List;
import java.util.Objects;
@@ -141,14 +141,14 @@ public class JdbcTable extends AbstractQueryableTable
}
SqlString generateSql() {
- final SqlNodeList selectList =
- new SqlNodeList(
- Collections.singletonList(SqlIdentifier.star(SqlParserPos.ZERO)),
- SqlParserPos.ZERO);
+ final SqlNodeList selectList = SqlNodeList.SINGLETON_STAR;
SqlSelect node =
new SqlSelect(SqlParserPos.ZERO, SqlNodeList.EMPTY, selectList,
tableName(), null, null, null, null, null, null, null, null);
- final SqlPrettyWriter writer = new SqlPrettyWriter(jdbcSchema.dialect);
+ final SqlWriterConfig config = SqlPrettyWriter.config()
+ .withAlwaysUseParentheses(true)
+ .withDialect(jdbcSchema.dialect);
+ final SqlPrettyWriter writer = new SqlPrettyWriter(config);
node.unparse(writer, 0, 0);
return writer.toSqlString();
}
diff --git a/core/src/main/java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java b/core/src/main/java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java
index e41b1ce..d499a1c 100644
--- a/core/src/main/java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java
+++ b/core/src/main/java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java
@@ -521,8 +521,7 @@ public class RelToSqlConverter extends SqlImplementor
// Wrap "SELECT 1 AS x"
// as "SELECT * FROM (SELECT 1 AS x) AS t WHERE false"
- query = new SqlSelect(POS, null,
- new SqlNodeList(ImmutableList.of(SqlIdentifier.star(POS)), POS),
+ query = new SqlSelect(POS, null, SqlNodeList.SINGLETON_STAR,
as(query, "t"), createAlwaysFalseCondition(), null, null,
null, null, null, null, null);
} else {
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlAsOperator.java b/core/src/main/java/org/apache/calcite/sql/SqlAsOperator.java
index 5e2b7cf..e90340d 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlAsOperator.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlAsOperator.java
@@ -72,7 +72,7 @@ public class SqlAsOperator extends SqlSpecialOperator {
assert call.operandCount() >= 2;
final SqlWriter.Frame frame =
writer.startList(
- SqlWriter.FrameTypeEnum.SIMPLE);
+ SqlWriter.FrameTypeEnum.AS);
call.operand(0).unparse(writer, leftPrec, getLeftPrec());
final boolean needsSpace = true;
writer.setNeedWhitespace(needsSpace);
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlJoin.java b/core/src/main/java/org/apache/calcite/sql/SqlJoin.java
index 1977aa6..21663e8 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlJoin.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlJoin.java
@@ -196,37 +196,32 @@ public class SqlJoin extends SqlCall {
int rightPrec) {
final SqlJoin join = (SqlJoin) call;
- final SqlWriter.Frame joinFrame =
- writer.startList(SqlWriter.FrameTypeEnum.JOIN);
join.left.unparse(
writer,
leftPrec,
getLeftPrec());
- String natural = "";
- if (join.isNatural()) {
- natural = "NATURAL ";
- }
switch (join.getJoinType()) {
case COMMA:
writer.sep(",", true);
break;
case CROSS:
- writer.sep(natural + "CROSS JOIN");
+ writer.sep(join.isNatural() ? "NATURAL CROSS JOIN" : "CROSS JOIN");
break;
case FULL:
- writer.sep(natural + "FULL JOIN");
+ writer.sep(join.isNatural() ? "NATURAL FULL JOIN" : "FULL JOIN");
break;
case INNER:
- writer.sep(natural + "INNER JOIN");
+ writer.sep(join.isNatural() ? "NATURAL INNER JOIN" : "INNER JOIN");
break;
case LEFT:
- writer.sep(natural + "LEFT JOIN");
+ writer.sep(join.isNatural() ? "NATURAL LEFT JOIN" : "LEFT JOIN");
break;
case LEFT_SEMI_JOIN:
- writer.sep(natural + "LEFT SEMI JOIN");
+ writer.sep(join.isNatural() ? "NATURAL LEFT SEMI JOIN"
+ : "LEFT SEMI JOIN");
break;
case RIGHT:
- writer.sep(natural + "RIGHT JOIN");
+ writer.sep(join.isNatural() ? "NATURAL RIGHT JOIN" : "RIGHT JOIN");
break;
default:
throw Util.unexpected(join.getJoinType());
@@ -254,7 +249,6 @@ public class SqlJoin extends SqlCall {
throw Util.unexpected(join.getConditionType());
}
}
- writer.endList(joinFrame);
}
}
}
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlMatchRecognize.java b/core/src/main/java/org/apache/calcite/sql/SqlMatchRecognize.java
index ecbc106..c4c156b 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlMatchRecognize.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlMatchRecognize.java
@@ -336,10 +336,8 @@ public class SqlMatchRecognize extends SqlCall {
if (pattern.orderList != null && pattern.orderList.size() > 0) {
writer.newlineAndIndent();
writer.sep("ORDER BY");
- final SqlWriter.Frame orderFrame =
- writer.startList(SqlWriter.FrameTypeEnum.ORDER_BY_LIST);
- unparseListClause(writer, pattern.orderList);
- writer.endList(orderFrame);
+ writer.list(SqlWriter.FrameTypeEnum.ORDER_BY_LIST, SqlWriter.COMMA,
+ pattern.orderList);
}
if (pattern.measureList != null && pattern.measureList.size() > 0) {
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlNode.java b/core/src/main/java/org/apache/calcite/sql/SqlNode.java
index ca3434c..9947f78 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlNode.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlNode.java
@@ -32,6 +32,7 @@ import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Set;
+import java.util.function.UnaryOperator;
import javax.annotation.Nonnull;
/**
@@ -122,7 +123,33 @@ public abstract class SqlNode implements Cloneable {
}
public String toString() {
- return toSqlString(null).getSql();
+ return toSqlString(c -> c.withDialect(AnsiSqlDialect.DEFAULT)
+ .withAlwaysUseParentheses(false)
+ .withSelectListItemsOnSeparateLines(false)
+ .withUpdateSetListNewline(false)
+ .withIndentation(0)).getSql();
+ }
+
+ /**
+ * Returns the SQL text of the tree of which this <code>SqlNode</code> is
+ * the root.
+ *
+ * <p>Typical return values are:
+ *
+ * <ul>
+ * <li>'It''s a bird!'
+ * <li>NULL
+ * <li>12.3
+ * <li>DATE '1969-04-29'
+ * </ul>
+ *
+ * @param transform Transform that sets desired writer configuration
+ */
+ public SqlString toSqlString(UnaryOperator<SqlWriterConfig> transform) {
+ final SqlWriterConfig config = transform.apply(SqlPrettyWriter.config());
+ SqlPrettyWriter writer = new SqlPrettyWriter(config);
+ unparse(writer, 0, 0);
+ return writer.toSqlString();
}
/**
@@ -143,15 +170,12 @@ public abstract class SqlNode implements Cloneable {
* useful for parse test, but false by default
*/
public SqlString toSqlString(SqlDialect dialect, boolean forceParens) {
- if (dialect == null) {
- dialect = AnsiSqlDialect.DEFAULT;
- }
- SqlPrettyWriter writer = new SqlPrettyWriter(dialect);
- writer.setAlwaysUseParentheses(forceParens);
- writer.setSelectListItemsOnSeparateLines(false);
- writer.setIndentation(0);
- unparse(writer, 0, 0);
- return writer.toSqlString();
+ return toSqlString(c ->
+ c.withDialect(Util.first(dialect, AnsiSqlDialect.DEFAULT))
+ .withAlwaysUseParentheses(forceParens)
+ .withSelectListItemsOnSeparateLines(false)
+ .withUpdateSetListNewline(false)
+ .withIndentation(0));
}
public SqlString toSqlString(SqlDialect dialect) {
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlNodeList.java b/core/src/main/java/org/apache/calcite/sql/SqlNodeList.java
index 426869d..d1a5329 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlNodeList.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlNodeList.java
@@ -16,13 +16,14 @@
*/
package org.apache.calcite.sql;
-import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.sql.parser.SqlParserPos;
import org.apache.calcite.sql.util.SqlVisitor;
import org.apache.calcite.sql.validate.SqlValidator;
import org.apache.calcite.sql.validate.SqlValidatorScope;
import org.apache.calcite.util.Litmus;
+import com.google.common.collect.ImmutableList;
+
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
@@ -45,6 +46,19 @@ public class SqlNodeList extends SqlNode implements Iterable<SqlNode> {
}
};
+ /**
+ * A SqlNodeList that has a single element that is an empty list.
+ */
+ public static final SqlNodeList SINGLETON_EMPTY =
+ new SqlNodeList(ImmutableList.of(EMPTY), SqlParserPos.ZERO);
+
+ /**
+ * A SqlNodeList that has a single element that is a star identifier.
+ */
+ public static final SqlNodeList SINGLETON_STAR =
+ new SqlNodeList(ImmutableList.of(SqlIdentifier.star(SqlParserPos.ZERO)),
+ SqlParserPos.ZERO);
+
//~ Instance fields --------------------------------------------------------
private final List<SqlNode> list;
@@ -105,42 +119,21 @@ public class SqlNodeList extends SqlNode implements Iterable<SqlNode> {
SqlWriter writer,
int leftPrec,
int rightPrec) {
- final SqlWriter.Frame frame =
- ((leftPrec > 0) || (rightPrec > 0)) ? writer.startList("(", ")")
- : writer.startList("", "");
- commaList(writer);
- writer.endList(frame);
+ final SqlWriter.FrameTypeEnum frameType =
+ (leftPrec > 0 || rightPrec > 0)
+ ? SqlWriter.FrameTypeEnum.PARENTHESES
+ : SqlWriter.FrameTypeEnum.SIMPLE;
+ writer.list(frameType, SqlWriter.COMMA, this);
}
+ @Deprecated // to be removed before 2.0
void commaList(SqlWriter writer) {
- // The precedence of the comma operator if low but not zero. For
- // instance, this ensures parentheses in
- // select x, (select * from foo order by z), y from t
- for (SqlNode node : list) {
- writer.sep(",");
- node.unparse(writer, 2, 3);
- }
+ unparse(writer, 0, 0);
}
- void andOrList(SqlWriter writer, SqlKind sepKind) {
- SqlBinaryOperator sepOp =
- sepKind == SqlKind.AND
- ? SqlStdOperatorTable.AND
- : SqlStdOperatorTable.OR;
- for (int i = 0; i < list.size(); i++) {
- SqlNode node = list.get(i);
- writer.sep(sepKind.name(), false);
-
- // The precedence pulling on the LHS of a node is the
- // right-precedence of the separator operator, except at the start
- // of the list; similarly for the RHS of a node. If the operator
- // has left precedence 4 and right precedence 5, the precedences
- // in a 3-node list will look as follows:
- // 0 <- node1 -> 4 5 <- node2 -> 4 5 <- node3 -> 0
- int lprec = (i == 0) ? 0 : sepOp.getRightPrec();
- int rprec = (i == (list.size() - 1)) ? 0 : sepOp.getLeftPrec();
- node.unparse(writer, lprec, rprec);
- }
+ @Deprecated // to be removed before 2.0
+ void andOrList(SqlWriter writer, SqlBinaryOperator sepOp) {
+ writer.list(SqlWriter.FrameTypeEnum.WHERE_LIST, sepOp, this);
}
public void validate(SqlValidator validator, SqlValidatorScope scope) {
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlOperator.java b/core/src/main/java/org/apache/calcite/sql/SqlOperator.java
index 1eb330d..3c96f02 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlOperator.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlOperator.java
@@ -19,6 +19,7 @@ package org.apache.calcite.sql;
import org.apache.calcite.linq4j.Ord;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeFactory;
+import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.sql.parser.SqlParserPos;
import org.apache.calcite.sql.type.SqlOperandTypeChecker;
import org.apache.calcite.sql.type.SqlOperandTypeInference;
@@ -332,25 +333,40 @@ public abstract class SqlOperator {
getSyntax().unparse(writer, this, call, leftPrec, rightPrec);
}
- // REVIEW jvs 9-June-2006: See http://issues.eigenbase.org/browse/FRG-149
- // for why this method exists.
+ @Deprecated // to be removed before 2.0
protected void unparseListClause(SqlWriter writer, SqlNode clause) {
- unparseListClause(writer, clause, null);
+ final SqlNodeList nodeList =
+ clause instanceof SqlNodeList
+ ? (SqlNodeList) clause
+ : SqlNodeList.of(clause);
+ writer.list(SqlWriter.FrameTypeEnum.SIMPLE, SqlWriter.COMMA, nodeList);
}
+ @Deprecated // to be removed before 2.0
protected void unparseListClause(
SqlWriter writer,
SqlNode clause,
SqlKind sepKind) {
- if (clause instanceof SqlNodeList) {
- if (sepKind != null) {
- ((SqlNodeList) clause).andOrList(writer, sepKind);
- } else {
- ((SqlNodeList) clause).commaList(writer);
- }
+ final SqlNodeList nodeList =
+ clause instanceof SqlNodeList
+ ? (SqlNodeList) clause
+ : SqlNodeList.of(clause);
+ final SqlBinaryOperator sepOp;
+ if (sepKind == null) {
+ sepOp = SqlWriter.COMMA;
} else {
- clause.unparse(writer, 0, 0);
+ switch (sepKind) {
+ case AND:
+ sepOp = SqlStdOperatorTable.AND;
+ break;
+ case OR:
+ sepOp = SqlStdOperatorTable.OR;
+ break;
+ default:
+ throw new AssertionError();
+ }
}
+ writer.list(SqlWriter.FrameTypeEnum.SIMPLE, sepOp, nodeList);
}
// override Object
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlOrderBy.java b/core/src/main/java/org/apache/calcite/sql/SqlOrderBy.java
index 57c2a71..e21fe4d 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlOrderBy.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlOrderBy.java
@@ -90,10 +90,8 @@ public class SqlOrderBy extends SqlCall {
orderBy.query.unparse(writer, getLeftPrec(), getRightPrec());
if (orderBy.orderList != SqlNodeList.EMPTY) {
writer.sep(getName());
- final SqlWriter.Frame listFrame =
- writer.startList(SqlWriter.FrameTypeEnum.ORDER_BY_LIST);
- unparseListClause(writer, orderBy.orderList);
- writer.endList(listFrame);
+ writer.list(SqlWriter.FrameTypeEnum.ORDER_BY_LIST, SqlWriter.COMMA,
+ orderBy.orderList);
}
if (orderBy.offset != null) {
final SqlWriter.Frame frame2 =
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlSelectOperator.java b/core/src/main/java/org/apache/calcite/sql/SqlSelectOperator.java
index 8f7f602..f55072c 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlSelectOperator.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlSelectOperator.java
@@ -16,6 +16,7 @@
*/
package org.apache.calcite.sql;
+import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.sql.parser.SqlParserPos;
import org.apache.calcite.sql.type.ReturnTypes;
import org.apache.calcite.sql.util.SqlBasicVisitor;
@@ -133,6 +134,7 @@ public class SqlSelectOperator extends SqlOperator {
}
}
+ @SuppressWarnings("deprecation")
public void unparse(
SqlWriter writer,
SqlCall call,
@@ -155,14 +157,12 @@ public class SqlSelectOperator extends SqlOperator {
keyword.unparse(writer, 0, 0);
}
writer.topN(select.fetch, select.offset);
- SqlNode selectClause = select.selectList;
- if (selectClause == null) {
- selectClause = SqlIdentifier.star(SqlParserPos.ZERO);
- }
- final SqlWriter.Frame selectListFrame =
- writer.startList(SqlWriter.FrameTypeEnum.SELECT_LIST);
- unparseListClause(writer, selectClause);
- writer.endList(selectListFrame);
+ final SqlNodeList selectClause =
+ select.selectList != null
+ ? select.selectList
+ : SqlNodeList.of(SqlIdentifier.star(SqlParserPos.ZERO));
+ writer.list(SqlWriter.FrameTypeEnum.SELECT_LIST, SqlWriter.COMMA,
+ selectClause);
if (select.from != null) {
// Calcite SQL requires FROM but MySQL does not.
@@ -187,15 +187,15 @@ public class SqlSelectOperator extends SqlOperator {
SqlNode node = select.where;
// decide whether to split on ORs or ANDs
- SqlKind whereSepKind = SqlKind.AND;
+ SqlBinaryOperator whereSep = SqlStdOperatorTable.AND;
if ((node instanceof SqlCall)
&& node.getKind() == SqlKind.OR) {
- whereSepKind = SqlKind.OR;
+ whereSep = SqlStdOperatorTable.OR;
}
// unroll whereClause
final List<SqlNode> list = new ArrayList<>(0);
- while (node.getKind() == whereSepKind) {
+ while (node.getKind() == whereSep.kind) {
assert node instanceof SqlCall;
final SqlCall call1 = (SqlCall) node;
list.add(0, call1.operand(1));
@@ -203,32 +203,20 @@ public class SqlSelectOperator extends SqlOperator {
}
list.add(0, node);
- // unparse in a WhereList frame
- final SqlWriter.Frame whereFrame =
- writer.startList(SqlWriter.FrameTypeEnum.WHERE_LIST);
- unparseListClause(
- writer,
- new SqlNodeList(
- list,
- select.where.getParserPosition()),
- whereSepKind);
- writer.endList(whereFrame);
+ // unparse in a WHERE_LIST frame
+ writer.list(SqlWriter.FrameTypeEnum.WHERE_LIST, whereSep,
+ new SqlNodeList(list, select.where.getParserPosition()));
} else {
select.where.unparse(writer, 0, 0);
}
}
if (select.groupBy != null) {
writer.sep("GROUP BY");
- final SqlWriter.Frame groupFrame =
- writer.startList(SqlWriter.FrameTypeEnum.GROUP_BY_LIST);
- if (select.groupBy.getList().isEmpty()) {
- final SqlWriter.Frame frame =
- writer.startList(SqlWriter.FrameTypeEnum.SIMPLE, "(", ")");
- writer.endList(frame);
- } else {
- unparseListClause(writer, select.groupBy);
- }
- writer.endList(groupFrame);
+ final SqlNodeList groupBy =
+ select.groupBy.size() == 0 ? SqlNodeList.SINGLETON_EMPTY
+ : select.groupBy;
+ writer.list(SqlWriter.FrameTypeEnum.GROUP_BY_LIST, SqlWriter.COMMA,
+ groupBy);
}
if (select.having != null) {
writer.sep("HAVING");
@@ -236,20 +224,13 @@ public class SqlSelectOperator extends SqlOperator {
}
if (select.windowDecls.size() > 0) {
writer.sep("WINDOW");
- final SqlWriter.Frame windowFrame =
- writer.startList(SqlWriter.FrameTypeEnum.WINDOW_DECL_LIST);
- for (SqlNode windowDecl : select.windowDecls) {
- writer.sep(",");
- windowDecl.unparse(writer, 0, 0);
- }
- writer.endList(windowFrame);
+ writer.list(SqlWriter.FrameTypeEnum.WINDOW_DECL_LIST, SqlWriter.COMMA,
+ select.windowDecls);
}
if (select.orderBy != null && select.orderBy.size() > 0) {
writer.sep("ORDER BY");
- final SqlWriter.Frame orderFrame =
- writer.startList(SqlWriter.FrameTypeEnum.ORDER_BY_LIST);
- unparseListClause(writer, select.orderBy);
- writer.endList(orderFrame);
+ writer.list(SqlWriter.FrameTypeEnum.ORDER_BY_LIST, SqlWriter.COMMA,
+ select.orderBy);
}
writer.fetchOffset(select.fetch, select.offset);
writer.endList(selectFrame);
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlWithinGroupOperator.java b/core/src/main/java/org/apache/calcite/sql/SqlWithinGroupOperator.java
index 5faaf64..b20fee9 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlWithinGroupOperator.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlWithinGroupOperator.java
@@ -48,7 +48,7 @@ public class SqlWithinGroupOperator extends SqlBinaryOperator {
final SqlWriter.Frame orderFrame =
writer.startList(SqlWriter.FrameTypeEnum.ORDER_BY_LIST, "(", ")");
writer.keyword("ORDER BY");
- ((SqlNodeList) call.operand(1)).commaList(writer);
+ ((SqlNodeList) call.operand(1)).unparse(writer, 0, 0);
writer.endList(orderFrame);
}
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlWriter.java b/core/src/main/java/org/apache/calcite/sql/SqlWriter.java
index 6ecc15c..b26489e 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlWriter.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlWriter.java
@@ -16,8 +16,11 @@
*/
package org.apache.calcite.sql;
+import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.sql.util.SqlString;
+import java.util.function.Consumer;
+
/**
* A <code>SqlWriter</code> is the target to construct a SQL statement from a
* parse tree. It deals with dialect differences; for example, Oracle quotes
@@ -72,6 +75,12 @@ public interface SqlWriter {
SIMPLE,
/**
+ * Comma-separated list surrounded by parentheses.
+ * The parentheses are present even if the list is empty.
+ */
+ PARENTHESES,
+
+ /**
* The SELECT clause of a SELECT statement.
*/
SELECT_LIST,
@@ -176,7 +185,7 @@ public interface SqlWriter {
* <li><code>GROUP BY x, FLOOR(y)</code></li>
* </ul>
*/
- SUB_QUERY,
+ SUB_QUERY(true),
/**
* Set operation.
@@ -221,7 +230,22 @@ public interface SqlWriter {
* <li><code>"A"."B"."C"</code></li>
* </ul>
*/
- IDENTIFIER(false);
+ IDENTIFIER(false),
+
+ /**
+ * Alias ("AS"). No indent.
+ */
+ AS(false),
+
+ /**
+ * CASE expression.
+ */
+ CASE,
+
+ /**
+ * Same behavior as user-defined frame type.
+ */
+ OTHER;
private final boolean needsIndent;
@@ -266,6 +290,18 @@ public interface SqlWriter {
}
}
+ /** Comma operator.
+ *
+ * <p>Defined in {@code SqlWriter} because it is only used while converting
+ * {@link SqlNode} to SQL;
+ * see {@link SqlWriter#list(FrameTypeEnum, SqlBinaryOperator, SqlNodeList)}.
+ *
+ * <p>The precedence of the comma operator is low but not zero. For
+ * instance, this ensures parentheses in
+ * {@code select x, (select * from foo order by z), y from t}. */
+ SqlBinaryOperator COMMA =
+ new SqlBinaryOperator(",", SqlKind.OTHER, 2, false, null, null, null);
+
//~ Methods ----------------------------------------------------------------
/**
@@ -407,6 +443,7 @@ public interface SqlWriter {
* Starts a list with no opening string.
*
* @param frameType Type of list. For example, a SELECT list will be
+ * governed according to SELECT-list formatting preferences.
*/
Frame startList(FrameTypeEnum frameType);
@@ -429,6 +466,20 @@ public interface SqlWriter {
void endList(Frame frame);
/**
+ * Writes a list.
+ */
+ SqlWriter list(FrameTypeEnum frameType, Consumer<SqlWriter> action);
+
+ /**
+ * Writes a list separated by a binary operator
+ * ({@link SqlStdOperatorTable#AND AND},
+ * {@link SqlStdOperatorTable#OR OR}, or
+ * {@link #COMMA COMMA}).
+ */
+ SqlWriter list(FrameTypeEnum frameType, SqlBinaryOperator sepOp,
+ SqlNodeList list);
+
+ /**
* Writes a list separator, unless the separator is "," and this is the
* first occurrence in the list.
*
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlWriterConfig.java b/core/src/main/java/org/apache/calcite/sql/SqlWriterConfig.java
new file mode 100644
index 0000000..7e9c1c6
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/sql/SqlWriterConfig.java
@@ -0,0 +1,434 @@
+/*
+ * 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.calcite.sql;
+
+import org.apache.calcite.sql.pretty.SqlPrettyWriter;
+import org.apache.calcite.util.ImmutableBeans;
+
+/** Configuration for {@link SqlWriter} and {@link SqlPrettyWriter}. */
+public interface SqlWriterConfig {
+ /** Returns the dialect. */
+ @ImmutableBeans.Property
+ SqlDialect dialect();
+
+ /** Sets {@link #dialect()}. */
+ SqlWriterConfig withDialect(SqlDialect dialect);
+
+ /** Returns whether to print keywords (SELECT, AS, etc.) in lower-case.
+ * Default is false: keywords are printed in upper-case. */
+ @ImmutableBeans.Property
+ @ImmutableBeans.BooleanDefault(false)
+ boolean keywordsLowerCase();
+
+ /** Sets {@link #keywordsLowerCase}. */
+ SqlWriterConfig withKeywordsLowerCase(boolean keywordsLowerCase);
+
+ /** Returns whether to quote all identifiers, even those which would be
+ * correct according to the rules of the {@link SqlDialect} if quotation
+ * marks were omitted. Default is true. */
+ @ImmutableBeans.Property
+ @ImmutableBeans.BooleanDefault(true)
+ boolean quoteAllIdentifiers();
+
+ /** Sets {@link #quoteAllIdentifiers}. */
+ SqlWriterConfig withQuoteAllIdentifiers(boolean quoteAllIdentifiers);
+
+ /** Returns the number of spaces indentation. Default is 4. */
+ @ImmutableBeans.Property
+ @ImmutableBeans.IntDefault(4)
+ int indentation();
+
+ /** Sets {@link #indentation}. */
+ SqlWriterConfig withIndentation(int indentation);
+
+ /** Returns whether a clause (FROM, WHERE, GROUP BY, HAVING, WINDOW,
+ * ORDER BY) starts a new line. Default is true. SELECT is always at the
+ * start of a line. */
+ @ImmutableBeans.Property
+ @ImmutableBeans.BooleanDefault(true)
+ boolean clauseStartsLine();
+
+ /** Sets {@link #clauseStartsLine}. */
+ SqlWriterConfig withClauseStartsLine(boolean clauseStartsLine);
+
+ /** Returns whether a clause (FROM, WHERE, GROUP BY, HAVING, WINDOW,
+ * ORDER BY) is followed by a new line. Default is false. */
+ @ImmutableBeans.Property
+ @ImmutableBeans.BooleanDefault(false)
+ boolean clauseEndsLine();
+
+ /** Sets {@link #clauseEndsLine()}. */
+ SqlWriterConfig withClauseEndsLine(boolean clauseEndsLine);
+
+ /** Returns whether each item in a SELECT list, GROUP BY list, or ORDER BY
+ * list is on its own line.
+ *
+ * <p>Default is false;
+ * this property is superseded by {@link #selectFolding()},
+ * {@link #groupByFolding()}, {@link #orderByFolding()}. */
+ @ImmutableBeans.Property
+ @ImmutableBeans.BooleanDefault(false)
+ boolean selectListItemsOnSeparateLines();
+
+ /** Sets {@link #selectListItemsOnSeparateLines}. */
+ SqlWriterConfig withSelectListItemsOnSeparateLines(
+ boolean selectListItemsOnSeparateLines);
+
+ /** Returns the line-folding policy for lists in the SELECT, GROUP BY and
+ * ORDER clauses, for items in the SET clause of UPDATE, and for items in
+ * VALUES.
+ *
+ * @see #foldLength()
+ *
+ * <p>If not set, the values of
+ * {@link #selectListItemsOnSeparateLines()},
+ * {@link #valuesListNewline()},
+ * {@link #updateSetListNewline()},
+ * {@link #windowDeclListNewline()} are used. */
+ @ImmutableBeans.Property
+ LineFolding lineFolding();
+
+ /** Sets {@link #lineFolding()}. */
+ SqlWriterConfig withLineFolding(LineFolding lineFolding);
+
+ /** Returns the line-folding policy for the SELECT clause.
+ * If not set, the value of {@link #lineFolding()} is used. */
+ @ImmutableBeans.Property
+ LineFolding selectFolding();
+
+ /** Sets {@link #selectFolding()}. */
+ SqlWriterConfig withSelectFolding(LineFolding lineFolding);
+
+ /** Returns the line-folding policy for the FROM clause (and JOIN).
+ * If not set, the value of {@link #lineFolding()} is used. */
+ @ImmutableBeans.Property
+ @ImmutableBeans.EnumDefault("TALL")
+ LineFolding fromFolding();
+
+ /** Sets {@link #fromFolding()}. */
+ SqlWriterConfig withFromFolding(LineFolding lineFolding);
+
+ /** Returns the line-folding policy for the WHERE clause.
+ * If not set, the value of {@link #lineFolding()} is used. */
+ @ImmutableBeans.Property
+ LineFolding whereFolding();
+
+ /** Sets {@link #whereFolding()}. */
+ SqlWriterConfig withWhereFolding(LineFolding lineFolding);
+
+ /** Returns the line-folding policy for the GROUP BY clause.
+ * If not set, the value of {@link #lineFolding()} is used. */
+ @ImmutableBeans.Property
+ LineFolding groupByFolding();
+
+ /** Sets {@link #groupByFolding()}. */
+ SqlWriterConfig withGroupByFolding(LineFolding lineFolding);
+
+ /** Returns the line-folding policy for the HAVING clause.
+ * If not set, the value of {@link #lineFolding()} is used. */
+ @ImmutableBeans.Property
+ LineFolding havingFolding();
+
+ /** Sets {@link #havingFolding()}. */
+ SqlWriterConfig withHavingFolding(LineFolding lineFolding);
+
+ /** Returns the line-folding policy for the WINDOW clause.
+ * If not set, the value of {@link #lineFolding()} is used. */
+ @ImmutableBeans.Property
+ LineFolding windowFolding();
+
+ /** Sets {@link #windowFolding()}. */
+ SqlWriterConfig withWindowFolding(LineFolding lineFolding);
+
+ /** Returns the line-folding policy for the MATCH_RECOGNIZE clause.
+ * If not set, the value of {@link #lineFolding()} is used. */
+ @ImmutableBeans.Property
+ LineFolding matchFolding();
+
+ /** Sets {@link #matchFolding()}. */
+ SqlWriterConfig withMatchFolding(LineFolding lineFolding);
+
+ /** Returns the line-folding policy for the ORDER BY clause.
+ * If not set, the value of {@link #lineFolding()} is used. */
+ @ImmutableBeans.Property
+ LineFolding orderByFolding();
+
+ /** Sets {@link #orderByFolding()}. */
+ SqlWriterConfig withOrderByFolding(LineFolding lineFolding);
+
+ /** Returns the line-folding policy for the OVER clause or a window
+ * declaration. If not set, the value of {@link #lineFolding()} is used. */
+ @ImmutableBeans.Property
+ LineFolding overFolding();
+
+ /** Sets {@link #overFolding()}. */
+ SqlWriterConfig withOverFolding(LineFolding lineFolding);
+
+ /** Returns the line-folding policy for the VALUES expression.
+ * If not set, the value of {@link #lineFolding()} is used. */
+ @ImmutableBeans.Property
+ LineFolding valuesFolding();
+
+ /** Sets {@link #valuesFolding()}. */
+ SqlWriterConfig withValuesFolding(LineFolding lineFolding);
+
+ /** Returns the line-folding policy for the SET clause of an UPDATE statement.
+ * If not set, the value of {@link #lineFolding()} is used. */
+ @ImmutableBeans.Property
+ LineFolding updateSetFolding();
+
+ /** Sets {@link #updateSetFolding()}. */
+ SqlWriterConfig withUpdateSetFolding(LineFolding lineFolding);
+
+ /**
+ * Returns whether to use a fix for SELECT list indentations.
+ *
+ * <ul>
+ * <li>If set to "false":
+ *
+ * <blockquote><pre>
+ * SELECT
+ * A as A,
+ * B as B,
+ * C as C,
+ * D
+ * </pre></blockquote>
+ *
+ * <li>If set to "true" (the default):
+ *
+ * <blockquote><pre>
+ * SELECT
+ * A as A,
+ * B as B,
+ * C as C,
+ * D
+ * </pre></blockquote>
+ * </ul>
+ */
+ @ImmutableBeans.Property
+ @ImmutableBeans.BooleanDefault(true)
+ boolean selectListExtraIndentFlag();
+
+ /** Sets {@link #selectListExtraIndentFlag}. */
+ SqlWriterConfig withSelectListExtraIndentFlag(boolean selectListExtraIndentFlag);
+
+ /** Returns whether each declaration in a WINDOW clause should be on its own
+ * line.
+ *
+ * <p>Default is true;
+ * this property is superseded by {@link #windowFolding()}. */
+ @ImmutableBeans.Property
+ @ImmutableBeans.BooleanDefault(true)
+ boolean windowDeclListNewline();
+
+ /** Sets {@link #windowDeclListNewline}. */
+ SqlWriterConfig withWindowDeclListNewline(boolean windowDeclListNewline);
+
+ /** Returns whether each row in a VALUES clause should be on its own
+ * line.
+ *
+ * <p>Default is true;
+ * this property is superseded by {@link #valuesFolding()}. */
+ @ImmutableBeans.Property
+ @ImmutableBeans.BooleanDefault(true)
+ boolean valuesListNewline();
+
+ /** Sets {@link #valuesListNewline}. */
+ SqlWriterConfig withValuesListNewline(boolean valuesListNewline);
+
+ /** Returns whether each assignment in the SET clause of an UPDATE or MERGE
+ * statement should be on its own line.
+ *
+ * <p>Default is true;
+ * this property is superseded by {@link #updateSetFolding()}. */
+ @ImmutableBeans.Property
+ @ImmutableBeans.BooleanDefault(true)
+ boolean updateSetListNewline();
+
+ /** Sets {@link #updateSetListNewline}. */
+ SqlWriterConfig withUpdateSetListNewline(boolean updateSetListNewline);
+
+ /** Returns whether a WINDOW clause should start its own line. */
+ @ImmutableBeans.Property
+ @ImmutableBeans.BooleanDefault(false)
+ boolean windowNewline();
+
+ /** Sets {@link #windowNewline}. */
+ SqlWriterConfig withWindowNewline(boolean windowNewline);
+
+ /** Returns whether commas in SELECT, GROUP BY and ORDER clauses should
+ * appear at the start of the line. Default is false. */
+ @ImmutableBeans.Property
+ @ImmutableBeans.BooleanDefault(false)
+ boolean leadingComma();
+
+ /** Sets {@link #leadingComma()}. */
+ SqlWriterConfig withLeadingComma(boolean leadingComma);
+
+ /** Returns the sub-query style.
+ * Default is {@link SqlWriter.SubQueryStyle#HYDE}. */
+ @ImmutableBeans.Property
+ @ImmutableBeans.EnumDefault("HYDE")
+ SqlWriter.SubQueryStyle subQueryStyle();
+
+ /** Sets {@link #subQueryStyle}. */
+ SqlWriterConfig withSubQueryStyle(SqlWriter.SubQueryStyle subQueryStyle);
+
+ /** Returns whether to print a newline before each AND or OR (whichever is
+ * higher level) in WHERE clauses.
+ *
+ * <p>NOTE: Ignored when alwaysUseParentheses is set to true. */
+ @ImmutableBeans.Property
+ @ImmutableBeans.BooleanDefault(false)
+ boolean whereListItemsOnSeparateLines();
+
+ /** Sets {@link #whereListItemsOnSeparateLines}. */
+ SqlWriterConfig withWhereListItemsOnSeparateLines(
+ boolean whereListItemsOnSeparateLines);
+
+ /** Returns whether expressions should always be included in parentheses.
+ * Default is false. */
+ @ImmutableBeans.Property
+ @ImmutableBeans.BooleanDefault(false)
+ boolean alwaysUseParentheses();
+
+ /** Sets {@link #alwaysUseParentheses}. */
+ SqlWriterConfig withAlwaysUseParentheses(boolean alwaysUseParentheses);
+
+ /** Returns the maximum line length. Default is zero, which means there is
+ * no maximum. */
+ @ImmutableBeans.Property
+ @ImmutableBeans.IntDefault(0)
+ int lineLength();
+
+ /** Sets {@link #lineLength}. */
+ SqlWriterConfig withLineLength(int lineLength);
+
+ /** Returns the line length at which items are chopped or folded (for clauses
+ * that have chosen {@link LineFolding#CHOP} or {@link LineFolding#FOLD}).
+ * Default is 80. */
+ @ImmutableBeans.Property
+ @ImmutableBeans.IntDefault(80)
+ int foldLength();
+
+ /** Sets {@link #foldLength()}. */
+ SqlWriterConfig withFoldLength(int lineLength);
+
+ /** Returns whether the WHEN, THEN and ELSE clauses of a CASE expression
+ * appear at the start of a new line. The default is false. */
+ @ImmutableBeans.Property
+ @ImmutableBeans.BooleanDefault(false)
+ boolean caseClausesOnNewLines();
+
+ /** Sets {@link #caseClausesOnNewLines}. */
+ SqlWriterConfig withCaseClausesOnNewLines(boolean caseClausesOnNewLines);
+
+ /** Policy for how to do deal with long lines.
+ *
+ * <p>The following examples all have
+ * {@link #clauseEndsLine ClauseEndsLine=true},
+ * {@link #indentation Indentation=4}, and
+ * {@link #foldLength FoldLength=25} (so that the long {@code SELECT}
+ * clause folds but the shorter {@code GROUP BY} clause does not).
+ *
+ * <p>Note that {@link #clauseEndsLine ClauseEndsLine} is observed in
+ * STEP and TALL modes, and in CHOP mode when a line is long.
+ *
+ * <table border=1>
+ * <caption>SQL formatted with each Folding value</caption>
+ * <tr>
+ * <th>Folding</th>
+ * <th>Example</th>
+ * </tr>
+ *
+ * <tr>
+ * <td>WIDE</td>
+ * <td><pre>
+ * SELECT abc, def, ghi, jkl, mno, pqr
+ * FROM t
+ * GROUP BY abc, def</pre></td>
+ * </tr>
+ *
+ * <tr>
+ * <td>STEP</td>
+ * <td><pre>
+ * SELECT
+ * abc, def, ghi, jkl, mno, pqr
+ * FROM t
+ * GROUP BY
+ * abc, def</pre></td>
+ * </tr>
+ *
+ * <tr>
+ * <td>FOLD</td>
+ * <td><pre>
+ * SELECT abc, def, ghi,
+ * jkl, mno, pqr
+ * FROM t
+ * GROUP BY abc, def</pre></td>
+ * </tr>
+ *
+ * <tr>
+ * <td>CHOP</td>
+ * <td><pre>
+ * SELECT
+ * abc,
+ * def,
+ * ghi,
+ * jkl,
+ * mno,
+ * pqr
+ * FROM t
+ * GROUP BY abc, def</pre></td>
+ * </tr>
+ *
+ * <tr>
+ * <td>TALL</td>
+ * <td><pre>
+ * SELECT
+ * abc,
+ * def,
+ * ghi,
+ * jkl,
+ * mno,
+ * pqr
+ * FROM t
+ * GROUP BY
+ * abc,
+ * def</pre></td>
+ * </tr>
+ * </table>
+ */
+ enum LineFolding {
+ /** Do not wrap. Items are on the same line, regardless of length. */
+ WIDE,
+
+ /** As {@link #WIDE} but start a new line if {@link #clauseEndsLine()}. */
+ STEP,
+
+ /** Wrap if long. Items are on the same line, but if the line's length
+ * exceeds {@link #foldLength()}, move items to the next line. */
+ FOLD,
+
+ /** Chop down if long. Items are on the same line, but if the line grows
+ * longer than {@link #foldLength()}, put all items on separate lines. */
+ CHOP,
+
+ /** Wrap always. Items are on separate lines. */
+ TALL
+ }
+}
diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlCaseOperator.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlCaseOperator.java
index bf87050..4a05992 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlCaseOperator.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlCaseOperator.java
@@ -127,9 +127,6 @@ import static org.apache.calcite.util.Static.RESOURCE;
public class SqlCaseOperator extends SqlOperator {
public static final SqlCaseOperator INSTANCE = new SqlCaseOperator();
- private static final SqlWriter.FrameType FRAME_TYPE =
- SqlWriter.FrameTypeEnum.create("CASE");
-
//~ Constructors -----------------------------------------------------------
private SqlCaseOperator() {
@@ -331,7 +328,7 @@ public class SqlCaseOperator extends SqlOperator {
int rightPrec) {
SqlCase kase = (SqlCase) call_;
final SqlWriter.Frame frame =
- writer.startList(FRAME_TYPE, "CASE", "END");
+ writer.startList(SqlWriter.FrameTypeEnum.CASE, "CASE", "END");
assert kase.whenList.size() == kase.thenList.size();
if (kase.value != null) {
kase.value.unparse(writer, 0, 0);
diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java
index 8899778..755fc4c 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java
@@ -74,6 +74,7 @@ import java.util.List;
* the standard operators and functions.
*/
public class SqlStdOperatorTable extends ReflectiveSqlOperatorTable {
+
//~ Static fields/initializers ---------------------------------------------
/**
diff --git a/core/src/main/java/org/apache/calcite/sql/pretty/SqlPrettyWriter.java b/core/src/main/java/org/apache/calcite/sql/pretty/SqlPrettyWriter.java
index f5a52f8..e29d2ef 100644
--- a/core/src/main/java/org/apache/calcite/sql/pretty/SqlPrettyWriter.java
+++ b/core/src/main/java/org/apache/calcite/sql/pretty/SqlPrettyWriter.java
@@ -17,12 +17,16 @@
package org.apache.calcite.sql.pretty;
import org.apache.calcite.avatica.util.Spaces;
+import org.apache.calcite.sql.SqlBinaryOperator;
import org.apache.calcite.sql.SqlDialect;
import org.apache.calcite.sql.SqlNode;
+import org.apache.calcite.sql.SqlNodeList;
import org.apache.calcite.sql.SqlWriter;
+import org.apache.calcite.sql.SqlWriterConfig;
import org.apache.calcite.sql.dialect.AnsiSqlDialect;
+import org.apache.calcite.sql.dialect.CalciteSqlDialect;
import org.apache.calcite.sql.util.SqlString;
-import org.apache.calcite.util.Unsafe;
+import org.apache.calcite.util.ImmutableBeans;
import org.apache.calcite.util.Util;
import org.apache.calcite.util.trace.CalciteLogger;
@@ -32,7 +36,6 @@ import com.google.common.collect.ImmutableList;
import org.slf4j.LoggerFactory;
import java.io.PrintWriter;
-import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayDeque;
@@ -44,6 +47,8 @@ import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.Set;
+import java.util.function.Consumer;
+import javax.annotation.Nonnull;
/**
* Pretty printer for SQL statements.
@@ -57,69 +62,189 @@ import java.util.Set;
* <th>Description</th>
* <th>Default</th>
* </tr>
+ *
* <tr>
- * <td>{@link #setSelectListItemsOnSeparateLines SelectListItemsOnSeparateLines}
- * </td>
- * <td>Whether each item in the select clause is on its own line</td>
+ * <td>{@link SqlWriterConfig#clauseStartsLine()} ClauseStartsLine}</td>
+ * <td>Whether a clause ({@code FROM}, {@code WHERE}, {@code GROUP BY},
+ * {@code HAVING}, {@code WINDOW}, {@code ORDER BY}) starts a new line.
+ * {@code SELECT} is always at the start of a line.</td>
+ * <td>true</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>{@link SqlWriterConfig#clauseEndsLine ClauseEndsLine}</td>
+ * <td>Whether a clause ({@code SELECT}, {@code FROM}, {@code WHERE},
+ * {@code GROUP BY}, {@code HAVING}, {@code WINDOW}, {@code ORDER BY}) is
+ * followed by a new line.</td>
* <td>false</td>
* </tr>
+ *
* <tr>
- * <td>{@link #setCaseClausesOnNewLines CaseClausesOnNewLines}</td>
+ * <td>{@link SqlWriterConfig#caseClausesOnNewLines CaseClausesOnNewLines}</td>
* <td>Whether the WHEN, THEN and ELSE clauses of a CASE expression appear at
* the start of a new line.</td>
* <td>false</td>
* </tr>
+ *
* <tr>
- * <td>{@link #setIndentation Indentation}</td>
+ * <td>{@link SqlWriterConfig#indentation Indentation}</td>
* <td>Number of spaces to indent</td>
* <td>4</td>
* </tr>
+ *
* <tr>
- * <td>{@link #setKeywordsLowerCase KeywordsLowerCase}</td>
+ * <td>{@link SqlWriterConfig#keywordsLowerCase KeywordsLowerCase}</td>
* <td>Whether to print keywords (SELECT, AS, etc.) in lower-case.</td>
* <td>false</td>
* </tr>
+ *
* <tr>
- * <td>{@link #isAlwaysUseParentheses ParenthesizeAllExprs}</td>
- * <td>Whether to enclose all expressions in parentheses, even if the operator
- * has high enough precedence that the parentheses are not required.
+ * <td>{@link SqlWriterConfig#alwaysUseParentheses AlwaysUseParentheses}</td>
+ * <td><p>Whether to enclose all expressions in parentheses, even if the
+ * operator has high enough precedence that the parentheses are not required.
*
- * <p>For example, the parentheses are required in the expression <code>(a + b)
- * c</code> because the '*' operator has higher precedence than the '+'
- * operator, and so without the parentheses, the expression would be equivalent
- * to <code>a + (b * c)</code>. The fully-parenthesized expression, <code>((a +
- * b) * c)</code> is unambiguous even if you don't know the precedence of every
- * operator.</td>
- * <td></td>
+ * <p>For example, the parentheses are required in the expression
+ * {@code (a + b) * c} because the '*' operator has higher precedence than the
+ * '+' operator, and so without the parentheses, the expression would be
+ * equivalent to {@code a + (b * c)}. The fully-parenthesized expression,
+ * {@code ((a + b) * c)} is unambiguous even if you don't know the precedence
+ * of every operator.</td>
+ * <td>false</td>
* </tr>
+ *
* <tr>
- * <td>{@link #setQuoteAllIdentifiers QuoteAllIdentifiers}</td>
+ * <td>{@link SqlWriterConfig#quoteAllIdentifiers QuoteAllIdentifiers}</td>
* <td>Whether to quote all identifiers, even those which would be correct
* according to the rules of the {@link SqlDialect} if quotation marks were
* omitted.</td>
* <td>true</td>
* </tr>
+ *
* <tr>
- * <td>{@link #setSelectListItemsOnSeparateLines SelectListItemsOnSeparateLines}
- * </td>
- * <td>Whether each item in the select clause is on its own line.</td>
- * <td>false</td>
- * </tr>
- * <tr>
- * <td>{@link #setSubQueryStyle SubQueryStyle}</td>
+ * <td>{@link SqlWriterConfig#subQueryStyle SubQueryStyle}</td>
* <td>Style for formatting sub-queries. Values are:
* {@link org.apache.calcite.sql.SqlWriter.SubQueryStyle#HYDE Hyde},
* {@link org.apache.calcite.sql.SqlWriter.SubQueryStyle#BLACK Black}.</td>
*
* <td>{@link org.apache.calcite.sql.SqlWriter.SubQueryStyle#HYDE Hyde}</td>
* </tr>
+ *
* <tr>
- * <td>{@link #setLineLength LineLength}</td>
- * <td>Set the desired maximum length for lines (to look nice in editors,
+ * <td>{@link SqlWriterConfig#lineLength LineLength}</td>
+ * <td>The desired maximum length for lines (to look nice in editors,
* printouts, etc.).</td>
- * <td>0</td>
+ * <td>-1 (no maximum)</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>{@link SqlWriterConfig#foldLength FoldLength}</td>
+ * <td>The line length at which lines are folded or chopped down
+ * (see {@code LineFolding}). Only has an effect if clauses are marked
+ * {@link SqlWriterConfig.LineFolding#CHOP CHOP} or
+ * {@link SqlWriterConfig.LineFolding#FOLD FOLD}.</td>
+ * <td>80</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>{@link SqlWriterConfig#lineFolding LineFolding}</td>
+ * <td>How long lines are to be handled. Options are lines are
+ * WIDE (do not wrap),
+ * FOLD (wrap if long),
+ * CHOP (chop down if long),
+ * and TALL (wrap always).</td>
+ * <td>WIDE</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>{@link SqlWriterConfig#selectFolding() SelectFolding}</td>
+ * <td>How the {@code SELECT} clause is to be folded.</td>
+ * <td>{@code LineFolding}</td>
* </tr>
+ *
+ * <tr>
+ * <td>{@link SqlWriterConfig#fromFolding FromFolding}</td>
+ * <td>How the {@code FROM} clause and nested {@code JOIN} clauses are to be
+ * folded.</td>
+ * <td>{@code LineFolding}</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>{@link SqlWriterConfig#whereFolding WhereFolding}</td>
+ * <td>How the {@code WHERE} clause is to be folded.</td>
+ * <td>{@code LineFolding}</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>{@link SqlWriterConfig#groupByFolding GroupByFolding}</td>
+ * <td>How the {@code GROUP BY} clause is to be folded.</td>
+ * <td>{@code LineFolding}</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>{@link SqlWriterConfig#havingFolding HavingFolding}</td>
+ * <td>How the {@code HAVING} clause is to be folded.</td>
+ * <td>{@code LineFolding}</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>{@link SqlWriterConfig#orderByFolding OrderByFolding}</td>
+ * <td>How the {@code ORDER BY} clause is to be folded.</td>
+ * <td>{@code LineFolding}</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>{@link SqlWriterConfig#windowFolding WindowFolding}</td>
+ * <td>How the {@code WINDOW} clause is to be folded.</td>
+ * <td>{@code LineFolding}</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>{@link SqlWriterConfig#overFolding OverFolding}</td>
+ * <td>How window declarations in the {@code WINDOW} clause
+ * and in the {@code OVER} clause of aggregate functions are to be folded.</td>
+ * <td>{@code LineFolding}</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>{@link SqlWriterConfig#valuesFolding ValuesFolding}</td>
+ * <td>How lists of values in the {@code VALUES} clause are to be folded.</td>
+ * <td>{@code LineFolding}</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>{@link SqlWriterConfig#updateSetFolding UpdateSetFolding}</td>
+ * <td>How assignments in the {@code SET} clause of an {@code UPDATE} statement
+ * are to be folded.</td>
+ * <td>{@code LineFolding}</td>
+ * </tr>
+ *
* </table>
+ *
+ * <p>The following options exist for backwards compatibility. They are
+ * used if {@link SqlWriterConfig#lineFolding LineFolding} and clause-specific
+ * options such as {@link SqlWriterConfig#selectFolding SelectFolding} are not
+ * specified:
+ *
+ * <ul>
+ *
+ * <li>{@link SqlWriterConfig#selectListItemsOnSeparateLines SelectListItemsOnSeparateLines}
+ * replaced by {@link SqlWriterConfig#selectFolding SelectFolding},
+ * {@link SqlWriterConfig#groupByFolding GroupByFolding}, and
+ * {@link SqlWriterConfig#orderByFolding OrderByFolding};
+ *
+ * <li>{@link SqlWriterConfig#updateSetListNewline UpdateSetListNewline}
+ * replaced by {@link SqlWriterConfig#updateSetFolding UpdateSetFolding};
+ *
+ * <li>{@link SqlWriterConfig#windowDeclListNewline WindowDeclListNewline}
+ * replaced by {@link SqlWriterConfig#windowFolding WindowFolding};
+ *
+ * <li>{@link SqlWriterConfig#windowNewline WindowNewline}
+ * replaced by {@link SqlWriterConfig#overFolding OverFolding};
+ *
+ * <li>{@link SqlWriterConfig#valuesListNewline ValuesListNewline}
+ * replaced by {@link SqlWriterConfig#valuesFolding ValuesFolding}.
+ *
+ * </ul>
*/
public class SqlPrettyWriter implements SqlWriter {
//~ Static fields/initializers ---------------------------------------------
@@ -132,96 +257,133 @@ public class SqlPrettyWriter implements SqlWriter {
* Bean holding the default property values.
*/
private static final Bean DEFAULT_BEAN =
- new SqlPrettyWriter(AnsiSqlDialect.DEFAULT).getBean();
+ new SqlPrettyWriter(SqlPrettyWriter.config()
+ .withDialect(AnsiSqlDialect.DEFAULT)).getBean();
protected static final String NL = System.getProperty("line.separator");
//~ Instance fields --------------------------------------------------------
private final SqlDialect dialect;
- private final StringWriter sw = new StringWriter();
- protected final PrintWriter pw;
+ private final StringBuilder buf;
private final Deque<FrameImpl> listStack = new ArrayDeque<>();
private ImmutableList.Builder<Integer> dynamicParameters;
protected FrameImpl frame;
private boolean needWhitespace;
protected String nextWhitespace;
- protected boolean alwaysUseParentheses;
- private boolean keywordsLowerCase;
+ private SqlWriterConfig config;
private Bean bean;
- private boolean quoteAllIdentifiers;
- private int indentation;
- private boolean clauseStartsLine;
- private boolean selectListItemsOnSeparateLines;
- private boolean selectListExtraIndentFlag;
private int currentIndent;
- private boolean windowDeclListNewline;
- private boolean updateSetListNewline;
- private boolean windowNewline;
- private SubQueryStyle subQueryStyle;
- private boolean whereListItemsOnSeparateLines;
- private boolean caseClausesOnNewLines;
- private int lineLength;
- private int charCount;
+ private int lineStart;
//~ Constructors -----------------------------------------------------------
+ private SqlPrettyWriter(SqlWriterConfig config,
+ StringBuilder buf, boolean ignore) {
+ this.buf = Objects.requireNonNull(buf);
+ this.dialect = Objects.requireNonNull(config.dialect());
+ this.config = Objects.requireNonNull(config);
+ lineStart = 0;
+ reset();
+ }
+
+ /** Creates a writer with the given configuration
+ * and a given buffer to write to. */
+ public SqlPrettyWriter(@Nonnull SqlWriterConfig config,
+ @Nonnull StringBuilder buf) {
+ this(config, Objects.requireNonNull(buf), false);
+ }
+
+ /** Creates a writer with the given configuration and dialect,
+ * and a given print writer (or a private print writer if it is null). */
+ public SqlPrettyWriter(
+ SqlDialect dialect,
+ SqlWriterConfig config,
+ StringBuilder buf) {
+ this(config.withDialect(Objects.requireNonNull(dialect)), buf);
+ }
+
+ /** Creates a writer with the given configuration
+ * and a private print writer. */
+ @Deprecated
+ public SqlPrettyWriter(SqlDialect dialect, SqlWriterConfig config) {
+ this(config.withDialect(Objects.requireNonNull(dialect)));
+ }
+
+ @Deprecated
public SqlPrettyWriter(
SqlDialect dialect,
boolean alwaysUseParentheses,
PrintWriter pw) {
- if (pw == null) {
- pw = new PrintWriter(sw);
- }
- this.pw = pw;
- this.dialect = dialect;
- this.alwaysUseParentheses = alwaysUseParentheses;
- resetSettings();
- reset();
+ // NOTE that 'pw' is ignored; there is no place for it in the new API
+ this(config().withDialect(Objects.requireNonNull(dialect))
+ .withAlwaysUseParentheses(alwaysUseParentheses));
}
+ @Deprecated
public SqlPrettyWriter(
SqlDialect dialect,
boolean alwaysUseParentheses) {
- this(dialect, alwaysUseParentheses, null);
+ this(config().withDialect(Objects.requireNonNull(dialect))
+ .withAlwaysUseParentheses(alwaysUseParentheses));
}
+ /** Creates a writer with a given dialect, the default configuration
+ * and a private print writer. */
+ @Deprecated
public SqlPrettyWriter(SqlDialect dialect) {
- this(dialect, true);
+ this(config().withDialect(Objects.requireNonNull(dialect)));
+ }
+
+ /** Creates a writer with the given configuration,
+ * and a private builder. */
+ public SqlPrettyWriter(@Nonnull SqlWriterConfig config) {
+ this(config, new StringBuilder(), true);
+ }
+
+ /** Creates a writer with the default configuration.
+ *
+ * @see #config() */
+ public SqlPrettyWriter() {
+ this(config());
+ }
+
+ /** Creates a {@link SqlWriterConfig} with Calcite's SQL dialect. */
+ public static SqlWriterConfig config() {
+ return ImmutableBeans.create(SqlWriterConfig.class)
+ .withDialect(CalciteSqlDialect.DEFAULT);
}
//~ Methods ----------------------------------------------------------------
- /**
- * Sets whether the WHEN, THEN and ELSE clauses of a CASE expression appear
- * at the start of a new line. The default is false.
- */
+ @Deprecated
public void setCaseClausesOnNewLines(boolean caseClausesOnNewLines) {
- this.caseClausesOnNewLines = caseClausesOnNewLines;
+ this.config = config.withCaseClausesOnNewLines(caseClausesOnNewLines);
}
- /**
- * Sets the sub-query style. Default is
- * {@link org.apache.calcite.sql.SqlWriter.SubQueryStyle#HYDE}.
- */
+ @Deprecated
public void setSubQueryStyle(SubQueryStyle subQueryStyle) {
- this.subQueryStyle = subQueryStyle;
+ this.config = config.withSubQueryStyle(subQueryStyle);
}
+ @Deprecated
public void setWindowNewline(boolean windowNewline) {
- this.windowNewline = windowNewline;
+ this.config = config.withWindowNewline(windowNewline);
}
+ @Deprecated
public void setWindowDeclListNewline(boolean windowDeclListNewline) {
- this.windowDeclListNewline = windowDeclListNewline;
+ this.config = config.withWindowDeclListNewline(windowDeclListNewline);
}
+ @Deprecated
public int getIndentation() {
- return indentation;
+ return config.indentation();
}
+ @Deprecated
public boolean isAlwaysUseParentheses() {
- return alwaysUseParentheses;
+ return config.alwaysUseParentheses();
}
public boolean inQuery() {
@@ -231,55 +393,49 @@ public class SqlPrettyWriter implements SqlWriter {
|| (frame.frameType == FrameTypeEnum.SETOP);
}
+ @Deprecated
public boolean isQuoteAllIdentifiers() {
- return quoteAllIdentifiers;
+ return config.quoteAllIdentifiers();
}
+ @Deprecated
public boolean isClauseStartsLine() {
- return clauseStartsLine;
+ return config.clauseStartsLine();
}
+ @Deprecated
public boolean isSelectListItemsOnSeparateLines() {
- return selectListItemsOnSeparateLines;
+ return config.selectListItemsOnSeparateLines();
}
+ @Deprecated
public boolean isWhereListItemsOnSeparateLines() {
- return whereListItemsOnSeparateLines;
+ return config.whereListItemsOnSeparateLines();
}
+ @Deprecated
public boolean isSelectListExtraIndentFlag() {
- return selectListExtraIndentFlag;
+ return config.selectListExtraIndentFlag();
}
+ @Deprecated
public boolean isKeywordsLowerCase() {
- return keywordsLowerCase;
+ return config.keywordsLowerCase();
}
+ @Deprecated
public int getLineLength() {
- return lineLength;
+ return config.lineLength();
}
public void resetSettings() {
reset();
- indentation = 4;
- clauseStartsLine = true;
- selectListItemsOnSeparateLines = false;
- selectListExtraIndentFlag = true;
- keywordsLowerCase = false;
- quoteAllIdentifiers = true;
- windowDeclListNewline = true;
- updateSetListNewline = true;
- windowNewline = false;
- subQueryStyle = SubQueryStyle.HYDE;
- alwaysUseParentheses = false;
- whereListItemsOnSeparateLines = false;
- lineLength = 0;
- charCount = 0;
+ config = config();
}
public void reset() {
- pw.flush();
- Unsafe.clear(sw);
+ buf.setLength(0);
+ lineStart = 0;
dynamicParameters = null;
setNeedWhitespace(false);
nextWhitespace = " ";
@@ -295,13 +451,9 @@ public class SqlPrettyWriter implements SqlWriter {
return bean;
}
- /**
- * Sets the number of spaces indentation.
- *
- * @see #getIndentation()
- */
+ @Deprecated
public void setIndentation(int indentation) {
- this.indentation = indentation;
+ this.config = config.withIndentation(indentation);
}
/**
@@ -343,78 +495,47 @@ public class SqlPrettyWriter implements SqlWriter {
}
}
- /**
- * Sets whether a clause (FROM, WHERE, GROUP BY, HAVING, WINDOW, ORDER BY)
- * starts a new line. Default is true. SELECT is always at the start of a
- * line.
- */
+ @Deprecated
public void setClauseStartsLine(boolean clauseStartsLine) {
- this.clauseStartsLine = clauseStartsLine;
+ this.config = config.withClauseStartsLine(clauseStartsLine);
}
- /**
- * Sets whether each item in a SELECT list, GROUP BY list, or ORDER BY list
- * is on its own line. Default false.
- */
+ @Deprecated
public void setSelectListItemsOnSeparateLines(boolean b) {
- this.selectListItemsOnSeparateLines = b;
+ this.config = config.withSelectListItemsOnSeparateLines(b);
}
- /**
- * Sets whether to use a fix for SELECT list indentations.
- *
- * <ul>
- * <li>If set to "false":
- *
- * <blockquote><pre>
- * SELECT
- * A as A
- * B as B
- * C as C
- * D
- * </pre></blockquote>
- *
- * <li>If set to "true":
- *
- * <blockquote><pre>
- * SELECT
- * A as A
- * B as B
- * C as C
- * D
- * </pre></blockquote>
- * </ul>
- */
- public void setSelectListExtraIndentFlag(boolean b) {
- this.selectListExtraIndentFlag = b;
+ @Deprecated
+ public void setSelectListExtraIndentFlag(boolean selectListExtraIndentFlag) {
+ this.config =
+ config.withSelectListExtraIndentFlag(selectListExtraIndentFlag);
}
- /**
- * Sets whether to print keywords (SELECT, AS, etc.) in lower-case. The
- * default is false: keywords are printed in upper-case.
- */
- public void setKeywordsLowerCase(boolean b) {
- this.keywordsLowerCase = b;
+ @Deprecated
+ public void setKeywordsLowerCase(boolean keywordsLowerCase) {
+ this.config = config.withKeywordsLowerCase(keywordsLowerCase);
}
- /**
- * Sets whether to print a newline before each AND or OR (whichever is
- * higher level) in WHERE clauses. NOTE: <i>Ignored when
- * alwaysUseParentheses is set to true.</i>
- */
-
- public void setWhereListItemsOnSeparateLines(boolean b) {
- this.whereListItemsOnSeparateLines = b;
+ @Deprecated
+ public void setWhereListItemsOnSeparateLines(
+ boolean whereListItemsOnSeparateLines) {
+ this.config =
+ config.withWhereListItemsOnSeparateLines(whereListItemsOnSeparateLines);
}
- public void setAlwaysUseParentheses(boolean b) {
- this.alwaysUseParentheses = b;
+ @Deprecated
+ public void setAlwaysUseParentheses(boolean alwaysUseParentheses) {
+ this.config = config.withAlwaysUseParentheses(alwaysUseParentheses);
}
public void newlineAndIndent() {
- pw.println();
- charCount = 0;
- indent(currentIndent);
+ newlineAndIndent(currentIndent);
+ }
+
+ public void newlineAndIndent(int indent) {
+ buf.append(NL);
+ lineStart = buf.length();
+ indent(indent);
setNeedWhitespace(false); // no further whitespace necessary
}
@@ -422,19 +543,12 @@ public class SqlPrettyWriter implements SqlWriter {
if (indent < 0) {
throw new IllegalArgumentException("negative indent " + indent);
}
- Spaces.append(pw, indent);
- charCount += indent;
+ Spaces.append(buf, indent);
}
- /**
- * Sets whether to quote all identifiers, even those which would be correct
- * according to the rules of the {@link SqlDialect} if quotation marks were
- * omitted.
- *
- * <p>Default true.
- */
- public void setQuoteAllIdentifiers(boolean b) {
- this.quoteAllIdentifiers = b;
+ @Deprecated
+ public void setQuoteAllIdentifiers(boolean quoteAllIdentifiers) {
+ this.config = config.withQuoteAllIdentifiers(quoteAllIdentifiers);
}
/**
@@ -454,283 +568,265 @@ public class SqlPrettyWriter implements SqlWriter {
String keyword,
String open,
String close) {
- int indentation = getIndentation();
- if (frameType instanceof FrameTypeEnum) {
- FrameTypeEnum frameTypeEnum = (FrameTypeEnum) frameType;
-
- switch (frameTypeEnum) {
- case WINDOW_DECL_LIST:
- case VALUES:
- return new FrameImpl(
- frameType,
- keyword,
- open,
- close,
- indentation,
- false,
- false,
- indentation,
- windowDeclListNewline,
- false,
- false);
-
- case UPDATE_SET_LIST:
- return new FrameImpl(
- frameType,
- keyword,
- open,
- close,
- indentation,
- false,
- updateSetListNewline,
- indentation,
- false,
- false,
- false);
-
- case SELECT_LIST:
- return new FrameImpl(
- frameType,
- keyword,
- open,
- close,
- selectListExtraIndentFlag ? indentation : 0,
- selectListItemsOnSeparateLines,
- false,
- indentation,
- selectListItemsOnSeparateLines,
- false,
- false);
-
- case ORDER_BY_LIST:
- case GROUP_BY_LIST:
- return new FrameImpl(
- frameType,
- keyword,
- open,
- close,
- indentation,
- selectListItemsOnSeparateLines,
- false,
- indentation,
- selectListItemsOnSeparateLines,
- false,
- false);
-
- case SUB_QUERY:
- switch (subQueryStyle) {
- case BLACK:
-
- // Generate, e.g.:
- //
- // WHERE foo = bar IN
- // ( SELECT ...
- open = Spaces.padRight("(", indentation);
- return new FrameImpl(
- frameType,
- keyword,
- open,
- close,
- 0,
- false,
- true,
- indentation,
- false,
- false,
- false) {
- protected void _before() {
- newlineAndIndent();
- }
- };
- case HYDE:
-
- // Generate, e.g.:
- //
- // WHERE foo IN (
- // SELECT ...
- return new FrameImpl(
- frameType,
- keyword,
- open,
- close,
- 0,
- false,
- true,
- 0,
- false,
- false,
- false) {
- protected void _before() {
- nextWhitespace = NL;
- }
- };
- default:
- throw Util.unexpected(subQueryStyle);
- }
-
- case ORDER_BY:
- case OFFSET:
- case FETCH:
- return new FrameImpl(
- frameType,
- keyword,
- open,
- close,
- 0,
- false,
- true,
- 0,
- false,
- false,
- false);
-
- case SELECT:
- return new FrameImpl(
- frameType,
- keyword,
- open,
- close,
- indentation,
- false,
- isClauseStartsLine(), // newline before FROM, WHERE etc.
- 0, // all clauses appear below SELECT
- false,
- false,
- false);
-
- case SETOP:
- return new FrameImpl(
- frameType,
- keyword,
- open,
- close,
- indentation,
- false,
- isClauseStartsLine(), // newline before UNION, EXCEPT
- 0, // all clauses appear below SELECT
- isClauseStartsLine(), // newline after UNION, EXCEPT
- false,
- false);
-
- case WINDOW:
- return new FrameImpl(
- frameType,
- keyword,
- open,
- close,
- indentation,
- false,
- windowNewline,
- 0,
- false,
- false,
- false);
-
- case FUN_CALL:
- setNeedWhitespace(false);
- return new FrameImpl(
- frameType,
- keyword,
- open,
- close,
- indentation,
- false,
- false,
- indentation,
- false,
- false,
- false);
-
- case IDENTIFIER:
- case SIMPLE:
- return new FrameImpl(
- frameType,
- keyword,
- open,
- close,
- indentation,
- false,
- false,
- indentation,
- false,
- false,
- false);
-
- case WHERE_LIST:
- return new FrameImpl(
- frameType,
- keyword,
- open,
- close,
- indentation,
- false,
- whereListItemsOnSeparateLines,
- 0,
- false,
- false,
- false);
-
- case FROM_LIST:
- case JOIN:
- return new FrameImpl(
- frameType,
- keyword,
- open,
- close,
- indentation,
- false,
- isClauseStartsLine(), // newline before UNION, EXCEPT
- 0, // all clauses appear below SELECT
- isClauseStartsLine(), // newline after UNION, EXCEPT
- false,
- false) {
- protected void sep(boolean printFirst, String sep) {
- boolean newlineBefore =
- newlineBeforeSep
- && !sep.equals(",");
- boolean newlineAfter =
- newlineAfterSep && sep.equals(",");
- if ((itemCount > 0) || printFirst) {
- if (newlineBefore && (itemCount > 0)) {
- pw.println();
- charCount = 0;
- indent(currentIndent + sepIndent);
- setNeedWhitespace(false);
- }
- keyword(sep);
- nextWhitespace = newlineAfter ? NL : " ";
- }
- ++itemCount;
- }
- };
- default:
- // fall through
- }
- }
+ final FrameTypeEnum frameTypeEnum =
+ frameType instanceof FrameTypeEnum ? (FrameTypeEnum) frameType
+ : FrameTypeEnum.OTHER;
+ final int indentation = config.indentation();
boolean newlineAfterOpen = false;
boolean newlineBeforeSep = false;
+ boolean newlineAfterSep = false;
boolean newlineBeforeClose = false;
+ int left = column();
int sepIndent = indentation;
- if (frameType.getName().equals("CASE")) {
- if (caseClausesOnNewLines) {
- newlineAfterOpen = true;
+ int extraIndent = 0;
+ final SqlWriterConfig.LineFolding fold = fold(frameTypeEnum);
+ final boolean newline = fold == SqlWriterConfig.LineFolding.TALL;
+
+ switch (frameTypeEnum) {
+ case SELECT:
+ extraIndent = indentation;
+ newlineAfterOpen = false;
+ newlineBeforeSep = config.clauseStartsLine(); // newline before FROM, WHERE etc.
+ newlineAfterSep = false;
+ sepIndent = 0; // all clauses appear below SELECT
+ break;
+
+ case SETOP:
+ extraIndent = 0;
+ newlineAfterOpen = false;
+ newlineBeforeSep = config.clauseStartsLine(); // newline before UNION, EXCEPT
+ newlineAfterSep = config.clauseStartsLine(); // newline after UNION, EXCEPT
+ sepIndent = 0; // all clauses appear below SELECT
+ break;
+
+ case SELECT_LIST:
+ case FROM_LIST:
+ case JOIN:
+ case GROUP_BY_LIST:
+ case ORDER_BY_LIST:
+ case WINDOW_DECL_LIST:
+ case VALUES:
+ if (config.selectListExtraIndentFlag()) {
+ extraIndent = indentation;
+ }
+ left = frame == null ? 0 : frame.left;
+ newlineAfterOpen = config.clauseEndsLine()
+ && (fold == SqlWriterConfig.LineFolding.TALL
+ || fold == SqlWriterConfig.LineFolding.STEP);
+ newlineBeforeSep = false;
+ newlineAfterSep = newline;
+ if (config.leadingComma() && newline) {
newlineBeforeSep = true;
- newlineBeforeClose = true;
- sepIndent = 0;
+ newlineAfterSep = false;
+ sepIndent = -", ".length();
}
+ break;
+
+ case WHERE_LIST:
+ case WINDOW:
+ extraIndent = indentation;
+ newlineAfterOpen = newline && config.clauseEndsLine();
+ newlineBeforeSep = newline;
+ sepIndent = 0;
+ newlineAfterSep = false;
+ break;
+
+ case ORDER_BY:
+ case OFFSET:
+ case FETCH:
+ newlineAfterOpen = false;
+ newlineBeforeSep = true;
+ sepIndent = 0;
+ newlineAfterSep = false;
+ break;
+
+ case UPDATE_SET_LIST:
+ extraIndent = indentation;
+ newlineAfterOpen = newline;
+ newlineBeforeSep = false;
+ sepIndent = 0;
+ newlineAfterSep = newline;
+ break;
+
+ case CASE:
+ newlineAfterOpen = newline;
+ newlineBeforeSep = newline;
+ newlineBeforeClose = newline;
+ sepIndent = 0;
+ break;
+ }
+
+ final int chopColumn;
+ final SqlWriterConfig.LineFolding lineFolding;
+ if (config.lineFolding() == null) {
+ lineFolding = SqlWriterConfig.LineFolding.WIDE;
+ chopColumn = -1;
+ } else {
+ lineFolding = config.lineFolding();
+ if (config.foldLength() > 0
+ && (lineFolding == SqlWriterConfig.LineFolding.CHOP
+ || lineFolding == SqlWriterConfig.LineFolding.FOLD
+ || lineFolding == SqlWriterConfig.LineFolding.STEP)) {
+ chopColumn = left + config.foldLength();
+ } else {
+ chopColumn = -1;
+ }
+ }
+
+ switch (frameTypeEnum) {
+ case UPDATE_SET_LIST:
+ case WINDOW_DECL_LIST:
+ case VALUES:
+ case SELECT:
+ case SETOP:
+ case SELECT_LIST:
+ case WHERE_LIST:
+ case ORDER_BY_LIST:
+ case GROUP_BY_LIST:
+ case WINDOW:
+ case ORDER_BY:
+ case OFFSET:
+ case FETCH:
+ return new FrameImpl(frameType, keyword, open, close,
+ left, extraIndent, chopColumn, lineFolding, newlineAfterOpen,
+ newlineBeforeSep, sepIndent, newlineAfterSep, false, false);
+
+ case SUB_QUERY:
+ switch (config.subQueryStyle()) {
+ case BLACK:
+ // Generate, e.g.:
+ //
+ // WHERE foo = bar IN
+ // ( SELECT ...
+ open = Spaces.padRight("(", indentation - 1);
+ return new FrameImpl(frameType, keyword, open, close,
+ left, 0, chopColumn, lineFolding, false, true, indentation,
+ false, false, false) {
+ protected void _before() {
+ newlineAndIndent();
+ }
+ };
+
+ case HYDE:
+ // Generate, e.g.:
+ //
+ // WHERE foo IN (
+ // SELECT ...
+ return new FrameImpl(frameType, keyword, open, close, left, 0,
+ chopColumn, lineFolding, false, true, 0, false, false, false) {
+ protected void _before() {
+ nextWhitespace = NL;
+ }
+ };
+
+ default:
+ throw Util.unexpected(config.subQueryStyle());
+ }
+
+ case FUN_CALL:
+ setNeedWhitespace(false);
+ return new FrameImpl(frameType, keyword, open, close,
+ left, indentation, chopColumn, lineFolding, false, false,
+ indentation, false, false, false);
+
+ case PARENTHESES:
+ open = "(";
+ close = ")";
+ // fall through
+ case IDENTIFIER:
+ case SIMPLE:
+ return new FrameImpl(frameType, keyword, open, close,
+ left, indentation, chopColumn, lineFolding, false, false,
+ indentation, false, false, false);
+
+ case FROM_LIST:
+ case JOIN:
+ newlineBeforeSep = newline;
+ sepIndent = 0; // all clauses appear below SELECT
+ newlineAfterSep = false;
+ final boolean newlineBeforeComma = config.leadingComma() && newline;
+ final boolean newlineAfterComma = !config.leadingComma() && newline;
+ return new FrameImpl(frameType, keyword, open, close, left,
+ indentation, chopColumn, lineFolding, newlineAfterOpen,
+ newlineBeforeSep, sepIndent, newlineAfterSep, false, false) {
+ protected void sep(boolean printFirst, String sep) {
+ final boolean newlineBeforeSep;
+ final boolean newlineAfterSep;
+ if (sep.equals(",")) {
+ newlineBeforeSep = newlineBeforeComma;
+ newlineAfterSep = newlineAfterComma;
+ } else {
+ newlineBeforeSep = this.newlineBeforeSep;
+ newlineAfterSep = this.newlineAfterSep;
+ }
+ if (itemCount == 0) {
+ if (newlineAfterOpen) {
+ newlineAndIndent(currentIndent);
+ }
+ } else {
+ if (newlineBeforeSep) {
+ newlineAndIndent(currentIndent + sepIndent);
+ }
+ }
+ if ((itemCount > 0) || printFirst) {
+ keyword(sep);
+ nextWhitespace = newlineAfterSep ? NL : " ";
+ }
+ ++itemCount;
+ }
+ };
+
+ default:
+ case OTHER:
+ return new FrameImpl(frameType, keyword, open, close, left, indentation,
+ chopColumn, lineFolding, newlineAfterOpen, newlineBeforeSep,
+ sepIndent, newlineAfterSep, newlineBeforeClose, false);
+ }
+ }
+
+ private SqlWriterConfig.LineFolding fold(FrameTypeEnum frameType) {
+ switch (frameType) {
+ case SELECT_LIST:
+ return f3(config.selectFolding(), config.lineFolding(),
+ config.selectListItemsOnSeparateLines());
+ case GROUP_BY_LIST:
+ return f3(config.groupByFolding(), config.lineFolding(),
+ config.selectListItemsOnSeparateLines());
+ case ORDER_BY_LIST:
+ return f3(config.orderByFolding(), config.lineFolding(),
+ config.selectListItemsOnSeparateLines());
+ case UPDATE_SET_LIST:
+ return f3(config.updateSetFolding(), config.lineFolding(),
+ config.updateSetListNewline());
+ case WHERE_LIST:
+ return f3(config.whereFolding(), config.lineFolding(),
+ config.whereListItemsOnSeparateLines());
+ case WINDOW_DECL_LIST:
+ return f3(config.windowFolding(), config.lineFolding(),
+ config.clauseStartsLine() && config.windowDeclListNewline());
+ case WINDOW:
+ return f3(config.overFolding(), config.lineFolding(),
+ config.windowNewline());
+ case VALUES:
+ return f3(config.valuesFolding(), config.lineFolding(),
+ config.valuesListNewline());
+ case FROM_LIST:
+ case JOIN:
+ return f3(config.fromFolding(), config.lineFolding(),
+ config.caseClausesOnNewLines());
+ case CASE:
+ return f3(null, null, config.caseClausesOnNewLines());
+ default:
+ return SqlWriterConfig.LineFolding.WIDE;
}
- return new FrameImpl(
- frameType,
- keyword,
- open,
- close,
- indentation,
- newlineAfterOpen,
- newlineBeforeSep,
- sepIndent,
- false,
- newlineBeforeClose,
- false);
+ }
+
+ private SqlWriterConfig.LineFolding f3(SqlWriterConfig.LineFolding folding0,
+ SqlWriterConfig.LineFolding folding1, boolean opt) {
+ return folding0 != null ? folding0
+ : folding1 != null ? folding1
+ : opt ? SqlWriterConfig.LineFolding.TALL
+ : SqlWriterConfig.LineFolding.WIDE;
}
/**
@@ -749,16 +845,19 @@ public class SqlPrettyWriter implements SqlWriter {
String close) {
assert frameType != null;
if (frame != null) {
- ++frame.itemCount;
+ if (frame.itemCount++ == 0 && frame.newlineAfterOpen) {
+ newlineAndIndent();
+ } else if (frameType == FrameTypeEnum.SUB_QUERY
+ && config.subQueryStyle() == SubQueryStyle.BLACK) {
+ newlineAndIndent(currentIndent - frame.extraIndent);
+ }
// REVIEW jvs 9-June-2006: This is part of the fix for FRG-149
// (extra frame for identifier was leading to extra indentation,
// causing select list to come out raggedy with identifiers
// deeper than literals); are there other frame types
// for which extra indent should be suppressed?
- if (frameType.needsIndent()) {
- currentIndent += frame.extraIndent;
- }
+ currentIndent += frame.extraIndent(frameType);
assert !listStack.contains(frame);
listStack.push(frame);
}
@@ -793,9 +892,7 @@ public class SqlPrettyWriter implements SqlWriter {
assert currentIndent == 0 : currentIndent;
} else {
this.frame = listStack.pop();
- if (endedFrame.frameType.needsIndent()) {
- currentIndent -= this.frame.extraIndent;
- }
+ currentIndent -= this.frame.extraIndent(endedFrame.frameType);
}
}
@@ -807,8 +904,7 @@ public class SqlPrettyWriter implements SqlWriter {
}
public String toString() {
- pw.flush();
- return sw.toString();
+ return buf.toString();
}
public SqlString toSqlString() {
@@ -828,11 +924,10 @@ public class SqlPrettyWriter implements SqlWriter {
public void keyword(String s) {
maybeWhitespace(s);
- pw.print(
+ buf.append(
isKeywordsLowerCase()
? s.toLowerCase(Locale.ROOT)
: s.toUpperCase(Locale.ROOT));
- charCount += s.length();
if (!s.equals("")) {
setNeedWhitespace(needWhitespaceAfter(s));
}
@@ -864,19 +959,24 @@ public class SqlPrettyWriter implements SqlWriter {
if (nextWhitespace.equals(NL)) {
newlineAndIndent();
} else {
- pw.print(nextWhitespace);
- charCount += nextWhitespace.length();
+ buf.append(nextWhitespace);
}
nextWhitespace = " ";
setNeedWhitespace(false);
}
}
+ /** Returns the number of characters appended since the last newline. */
+ private int column() {
+ return buf.length() - lineStart;
+ }
+
protected boolean tooLong(String s) {
+ final int lineLength = config.lineLength();
boolean result =
lineLength > 0
- && (charCount > currentIndent)
- && ((charCount + s.length()) >= lineLength);
+ && (column() > currentIndent)
+ && ((column() + s.length()) >= lineLength);
if (result) {
nextWhitespace = NL;
}
@@ -886,14 +986,12 @@ public class SqlPrettyWriter implements SqlWriter {
public void print(String s) {
maybeWhitespace(s);
- pw.print(s);
- charCount += s.length();
+ buf.append(s);
}
public void print(int x) {
maybeWhitespace("0");
- pw.print(x);
- charCount += String.valueOf(x).length();
+ buf.append(x);
}
public void identifier(String name, boolean quoted) {
@@ -904,8 +1002,7 @@ public class SqlPrettyWriter implements SqlWriter {
qName = dialect.quoteIdentifier(name);
}
maybeWhitespace(qName);
- pw.print(qName);
- charCount += qName.length();
+ buf.append(qName);
setNeedWhitespace(true);
}
@@ -956,6 +1053,23 @@ public class SqlPrettyWriter implements SqlWriter {
return startList(frameType, null, open, close);
}
+ public SqlWriter list(FrameTypeEnum frameType, Consumer<SqlWriter> action) {
+ final SqlWriter.Frame selectListFrame =
+ startList(SqlWriter.FrameTypeEnum.SELECT_LIST);
+ final SqlWriter w = this;
+ action.accept(w);
+ endList(selectListFrame);
+ return this;
+ }
+
+ public SqlWriter list(FrameTypeEnum frameType, SqlBinaryOperator sepOp,
+ SqlNodeList list) {
+ final SqlWriter.Frame frame = startList(frameType);
+ ((FrameImpl) frame).list(list, sepOp);
+ endList(frame);
+ return this;
+ }
+
public void sep(String sep) {
sep(sep, !(sep.equals(",") || sep.equals(".")));
}
@@ -974,8 +1088,9 @@ public class SqlPrettyWriter implements SqlWriter {
this.needWhitespace = needWhitespace;
}
+ @Deprecated
public void setLineLength(int lineLength) {
- this.lineLength = lineLength;
+ this.config = config.withLineLength(lineLength);
}
public void setFormatOptions(SqlFormatOptions options) {
@@ -1008,6 +1123,7 @@ public class SqlPrettyWriter implements SqlWriter {
final String open;
final String close;
+ private final int left;
/**
* Indent of sub-frame with respect to this one.
*/
@@ -1033,33 +1149,40 @@ public class SqlPrettyWriter implements SqlWriter {
* Whether to print a newline after each separator.
*/
public final boolean newlineAfterSep;
- private final boolean newlineBeforeClose;
- private final boolean newlineAfterClose;
- private final boolean newlineAfterOpen;
-
- FrameImpl(
- FrameType frameType,
- String keyword,
- String open,
- String close,
- int extraIndent,
- boolean newlineAfterOpen,
- boolean newlineBeforeSep,
- int sepIndent,
- boolean newlineAfterSep,
- boolean newlineBeforeClose,
- boolean newlineAfterClose) {
+ protected final boolean newlineBeforeClose;
+ protected final boolean newlineAfterClose;
+ protected final boolean newlineAfterOpen;
+
+ /** Character count after which we should move an item to the
+ * next line. Or {@link Integer#MAX_VALUE} if we are not chopping. */
+ private final int chopLimit;
+
+ /** How lines are to be folded. */
+ private final SqlWriterConfig.LineFolding lineFolding;
+
+ FrameImpl(FrameType frameType, String keyword, String open, String close,
+ int left, int extraIndent, int chopLimit,
+ SqlWriterConfig.LineFolding lineFolding, boolean newlineAfterOpen,
+ boolean newlineBeforeSep, int sepIndent, boolean newlineAfterSep,
+ boolean newlineBeforeClose, boolean newlineAfterClose) {
this.frameType = frameType;
this.keyword = keyword;
this.open = open;
this.close = close;
+ this.left = left;
this.extraIndent = extraIndent;
+ this.chopLimit = chopLimit;
+ this.lineFolding = lineFolding;
this.newlineAfterOpen = newlineAfterOpen;
this.newlineBeforeSep = newlineBeforeSep;
this.newlineAfterSep = newlineAfterSep;
this.newlineBeforeClose = newlineBeforeClose;
this.newlineAfterClose = newlineAfterClose;
this.sepIndent = sepIndent;
+ assert chopLimit >= 0
+ == (lineFolding == SqlWriterConfig.LineFolding.CHOP
+ || lineFolding == SqlWriterConfig.LineFolding.FOLD
+ || lineFolding == SqlWriterConfig.LineFolding.STEP);
}
protected void before() {
@@ -1072,9 +1195,14 @@ public class SqlPrettyWriter implements SqlWriter {
}
protected void sep(boolean printFirst, String sep) {
- if ((newlineBeforeSep && (itemCount > 0))
- || (newlineAfterOpen && (itemCount == 0))) {
- newlineAndIndent();
+ if (itemCount == 0) {
+ if (newlineAfterOpen) {
+ newlineAndIndent(currentIndent);
+ }
+ } else {
+ if (newlineBeforeSep) {
+ newlineAndIndent(currentIndent + sepIndent);
+ }
}
if ((itemCount > 0) || printFirst) {
keyword(sep);
@@ -1082,6 +1210,150 @@ public class SqlPrettyWriter implements SqlWriter {
}
++itemCount;
}
+
+ /** Returns the extra indent required for a given type of sub-frame. */
+ int extraIndent(FrameType subFrameType) {
+ if (this.frameType == FrameTypeEnum.ORDER_BY
+ && subFrameType == FrameTypeEnum.ORDER_BY_LIST) {
+ return config.indentation();
+ }
+ if (subFrameType.needsIndent()) {
+ return extraIndent;
+ }
+ return 0;
+ }
+
+ void list(SqlNodeList list, SqlBinaryOperator sepOp) {
+ final Save save;
+ switch (lineFolding) {
+ case CHOP:
+ case FOLD:
+ save = new Save();
+ if (!list2(list, sepOp)) {
+ save.restore();
+ final boolean newlineAfterOpen = config.clauseEndsLine();
+ final SqlWriterConfig.LineFolding lineFolding;
+ final int chopLimit;
+ if (this.lineFolding == SqlWriterConfig.LineFolding.CHOP) {
+ lineFolding = SqlWriterConfig.LineFolding.TALL;
+ chopLimit = -1;
+ } else {
+ lineFolding = SqlWriterConfig.LineFolding.FOLD;
+ chopLimit = this.chopLimit;
+ }
+ final boolean newline =
+ lineFolding == SqlWriterConfig.LineFolding.TALL;
+ final boolean newlineBeforeSep;
+ final boolean newlineAfterSep;
+ final int sepIndent;
+ if (config.leadingComma() && newline) {
+ newlineBeforeSep = true;
+ newlineAfterSep = false;
+ sepIndent = -", ".length();
+ } else if (newline) {
+ newlineBeforeSep = false;
+ newlineAfterSep = true;
+ sepIndent = this.sepIndent;
+ } else {
+ newlineBeforeSep = false;
+ newlineAfterSep = false;
+ sepIndent = this.sepIndent;
+ }
+ final FrameImpl frame2 =
+ new FrameImpl(frameType, keyword, open, close, left, extraIndent,
+ chopLimit, lineFolding, newlineAfterOpen, newlineBeforeSep,
+ sepIndent, newlineAfterSep, newlineBeforeClose,
+ newlineAfterClose);
+ frame2.list2(list, sepOp);
+ }
+ break;
+ default:
+ list2(list, sepOp);
+ }
+ }
+
+ /** Tries to write a list. If the line runs too long, returns false,
+ * indicating to retry. */
+ private boolean list2(SqlNodeList list, SqlBinaryOperator sepOp) {
+ // The precedence pulling on the LHS of a node is the
+ // right-precedence of the separator operator. Similarly RHS.
+ //
+ // At the start and end of the list precedence should be 0, but non-zero
+ // precedence is useful, because it forces parentheses around
+ // sub-queries and empty lists, e.g. "SELECT a, (SELECT * FROM t), b",
+ // "GROUP BY ()".
+ final int lprec = sepOp.getRightPrec();
+ final int rprec = sepOp.getLeftPrec();
+ if (chopLimit < 0) {
+ for (int i = 0; i < list.size(); i++) {
+ SqlNode node = list.get(i);
+ sep(false, sepOp.getName());
+ node.unparse(SqlPrettyWriter.this, lprec, rprec);
+ }
+ } else if (newlineBeforeSep) {
+ for (int i = 0; i < list.size(); i++) {
+ SqlNode node = list.get(i);
+ sep(false, sepOp.getName());
+ final Save prevSize = new Save();
+ node.unparse(SqlPrettyWriter.this, lprec, rprec);
+ if (column() > chopLimit) {
+ if (lineFolding == SqlWriterConfig.LineFolding.CHOP
+ || lineFolding == SqlWriterConfig.LineFolding.TALL) {
+ return false;
+ }
+ prevSize.restore();
+ newlineAndIndent();
+ node.unparse(SqlPrettyWriter.this, lprec, rprec);
+ }
+ }
+ } else {
+ for (int i = 0; i < list.size(); i++) {
+ SqlNode node = list.get(i);
+ if (i == 0) {
+ sep(false, sepOp.getName());
+ }
+ final Save save = new Save();
+ node.unparse(SqlPrettyWriter.this, lprec, rprec);
+ if (i + 1 < list.size()) {
+ sep(false, sepOp.getName());
+ }
+ if (column() > chopLimit) {
+ switch (lineFolding) {
+ case CHOP:
+ return false;
+ case FOLD:
+ if (newlineAfterOpen != config.clauseEndsLine()) {
+ return false;
+ }
+ }
+ save.restore();
+ newlineAndIndent();
+ node.unparse(SqlPrettyWriter.this, lprec, rprec);
+ if (i + 1 < list.size()) {
+ sep(false, sepOp.getName());
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ /** Remembers the state of the current frame and writer.
+ *
+ * <p>You can call {@link #restore} to restore to that state, or just
+ * continue. It is useful if you wish to re-try with different options
+ * (for example, with lines wrapped). */
+ class Save {
+ final int bufLength;
+
+ Save() {
+ this.bufLength = buf.length();
+ }
+
+ void restore() {
+ buf.setLength(bufLength);
+ }
+ }
}
/**
@@ -1160,4 +1432,5 @@ public class SqlPrettyWriter implements SqlWriter {
return names.toArray(new String[0]);
}
}
+
}
diff --git a/core/src/main/java/org/apache/calcite/sql/type/IntervalSqlType.java b/core/src/main/java/org/apache/calcite/sql/type/IntervalSqlType.java
index f427a31..c58240c 100644
--- a/core/src/main/java/org/apache/calcite/sql/type/IntervalSqlType.java
+++ b/core/src/main/java/org/apache/calcite/sql/type/IntervalSqlType.java
@@ -22,6 +22,7 @@ import org.apache.calcite.rel.type.RelDataTypeFactoryImpl;
import org.apache.calcite.rel.type.RelDataTypeSystem;
import org.apache.calcite.sql.SqlDialect;
import org.apache.calcite.sql.SqlIntervalQualifier;
+import org.apache.calcite.sql.SqlWriterConfig;
import org.apache.calcite.sql.dialect.AnsiSqlDialect;
import org.apache.calcite.sql.parser.SqlParserPos;
import org.apache.calcite.sql.pretty.SqlPrettyWriter;
@@ -58,10 +59,12 @@ public class IntervalSqlType extends AbstractSqlType {
protected void generateTypeString(StringBuilder sb, boolean withDetail) {
sb.append("INTERVAL ");
final SqlDialect dialect = AnsiSqlDialect.DEFAULT;
- final SqlPrettyWriter writer = new SqlPrettyWriter(dialect);
- writer.setAlwaysUseParentheses(false);
- writer.setSelectListItemsOnSeparateLines(false);
- writer.setIndentation(0);
+ final SqlWriterConfig config = SqlPrettyWriter.config()
+ .withAlwaysUseParentheses(false)
+ .withSelectListItemsOnSeparateLines(false)
+ .withIndentation(0)
+ .withDialect(dialect);
+ final SqlPrettyWriter writer = new SqlPrettyWriter(config);
intervalQualifier.unparse(writer, 0, 0);
final String sql = writer.toString();
sb.append(new SqlString(dialect, sql).getSql());
diff --git a/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java b/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java
index 9331b44..269bcb2 100644
--- a/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java
+++ b/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java
@@ -26,7 +26,8 @@ import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.SqlNodeList;
import org.apache.calcite.sql.SqlSetOption;
-import org.apache.calcite.sql.dialect.CalciteSqlDialect;
+import org.apache.calcite.sql.SqlWriterConfig;
+import org.apache.calcite.sql.dialect.AnsiSqlDialect;
import org.apache.calcite.sql.parser.impl.SqlParserImpl;
import org.apache.calcite.sql.pretty.SqlPrettyWriter;
import org.apache.calcite.sql.test.SqlTests;
@@ -59,6 +60,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
+import java.util.Random;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.function.Consumer;
@@ -70,6 +72,7 @@ import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assumptions.assumeFalse;
@@ -566,6 +569,13 @@ public class SqlParserTest {
private static final ThreadLocal<boolean[]> LINUXIFY =
ThreadLocal.withInitial(() -> new boolean[] {true});
+ private static final SqlWriterConfig SQL_WRITER_CONFIG =
+ SqlPrettyWriter.config()
+ .withAlwaysUseParentheses(true)
+ .withUpdateSetListNewline(false)
+ .withFromFolding(SqlWriterConfig.LineFolding.TALL)
+ .withIndentation(0);
+
Quoting quoting = Quoting.DOUBLE_QUOTE;
Casing unquotedCasing = Casing.TO_UPPER;
Casing quotedCasing = Casing.UNCHANGED;
@@ -3768,10 +3778,10 @@ public class SqlParserTest {
@Test public void testUpdateExtendedColumnList() {
final String expected = "UPDATE `EMPDEFAULTS` EXTEND (`EXTRA` BOOLEAN, `NOTE` VARCHAR)"
- + " SET `DEPTNO` = 1\n"
- + ", `EXTRA` = TRUE\n"
- + ", `EMPNO` = 20\n"
- + ", `ENAME` = 'Bob'\n"
+ + " SET `DEPTNO` = 1"
+ + ", `EXTRA` = TRUE"
+ + ", `EMPNO` = 20"
+ + ", `ENAME` = 'Bob'"
+ ", `NOTE` = 'legion'\n"
+ "WHERE (`DEPTNO` = 10)";
sql("update empdefaults(extra BOOLEAN, note VARCHAR)"
@@ -3783,10 +3793,10 @@ public class SqlParserTest {
@Test public void testUpdateCaseSensitiveExtendedColumnList() {
final String expected = "UPDATE `EMPDEFAULTS` EXTEND (`extra` BOOLEAN, `NOTE` VARCHAR)"
- + " SET `DEPTNO` = 1\n"
- + ", `extra` = TRUE\n"
- + ", `EMPNO` = 20\n"
- + ", `ENAME` = 'Bob'\n"
+ + " SET `DEPTNO` = 1"
+ + ", `extra` = TRUE"
+ + ", `EMPNO` = 20"
+ + ", `ENAME` = 'Bob'"
+ ", `NOTE` = 'legion'\n"
+ "WHERE (`DEPTNO` = 10)";
sql("update empdefaults(\"extra\" BOOLEAN, note VARCHAR)"
@@ -3866,7 +3876,7 @@ public class SqlParserTest {
@Test public void testUpdate() {
sql("update emps set empno = empno + 1, sal = sal - 1 where empno=12")
- .ok("UPDATE `EMPS` SET `EMPNO` = (`EMPNO` + 1)\n"
+ .ok("UPDATE `EMPS` SET `EMPNO` = (`EMPNO` + 1)"
+ ", `SAL` = (`SAL` - 1)\n"
+ "WHERE (`EMPNO` = 12)");
}
@@ -3884,8 +3894,8 @@ public class SqlParserTest {
+ "FROM `TEMPEMPS`\n"
+ "WHERE (`DEPTNO` IS NULL)) AS `T`\n"
+ "ON (`E`.`EMPNO` = `T`.`EMPNO`)\n"
- + "WHEN MATCHED THEN UPDATE SET `NAME` = `T`.`NAME`\n"
- + ", `DEPTNO` = `T`.`DEPTNO`\n"
+ + "WHEN MATCHED THEN UPDATE SET `NAME` = `T`.`NAME`"
+ + ", `DEPTNO` = `T`.`DEPTNO`"
+ ", `SALARY` = (`T`.`SALARY` * 0.1)\n"
+ "WHEN NOT MATCHED THEN INSERT (`NAME`, `DEPT`, `SALARY`) "
+ "(VALUES (ROW(`T`.`NAME`, 10, (`T`.`SALARY` * 0.15))))";
@@ -3907,8 +3917,8 @@ public class SqlParserTest {
+ "FROM `TEMPEMPS`\n"
+ "WHERE (`DEPTNO` IS NULL)) AS `T`\n"
+ "ON (`E`.`EMPNO` = `T`.`EMPNO`)\n"
- + "WHEN MATCHED THEN UPDATE SET `E`.`NAME` = `T`.`NAME`\n"
- + ", `E`.`DEPTNO` = `T`.`DEPTNO`\n"
+ + "WHEN MATCHED THEN UPDATE SET `E`.`NAME` = `T`.`NAME`"
+ + ", `E`.`DEPTNO` = `T`.`DEPTNO`"
+ ", `E`.`SALARY` = (`T`.`SALARY` * 0.1)\n"
+ "WHEN NOT MATCHED THEN INSERT (`NAME`, `DEPT`, `SALARY`) "
+ "(VALUES (ROW(`T`.`NAME`, 10, (`T`.`SALARY` * 0.15))))";
@@ -3927,8 +3937,8 @@ public class SqlParserTest {
final String expected = "MERGE INTO `EMPS` AS `E`\n"
+ "USING `TEMPEMPS` AS `T`\n"
+ "ON (`E`.`EMPNO` = `T`.`EMPNO`)\n"
- + "WHEN MATCHED THEN UPDATE SET `NAME` = `T`.`NAME`\n"
- + ", `DEPTNO` = `T`.`DEPTNO`\n"
+ + "WHEN MATCHED THEN UPDATE SET `NAME` = `T`.`NAME`"
+ + ", `DEPTNO` = `T`.`DEPTNO`"
+ ", `SALARY` = (`T`.`SALARY` * 0.1)\n"
+ "WHEN NOT MATCHED THEN INSERT (`NAME`, `DEPT`, `SALARY`) "
+ "(VALUES (ROW(`T`.`NAME`, 10, (`T`.`SALARY` * 0.15))))";
@@ -3947,8 +3957,8 @@ public class SqlParserTest {
final String expected = "MERGE INTO `EMPS` AS `E`\n"
+ "USING `TEMPEMPS` AS `T`\n"
+ "ON (`E`.`EMPNO` = `T`.`EMPNO`)\n"
- + "WHEN MATCHED THEN UPDATE SET `E`.`NAME` = `T`.`NAME`\n"
- + ", `E`.`DEPTNO` = `T`.`DEPTNO`\n"
+ + "WHEN MATCHED THEN UPDATE SET `E`.`NAME` = `T`.`NAME`"
+ + ", `E`.`DEPTNO` = `T`.`DEPTNO`"
+ ", `E`.`SALARY` = (`T`.`SALARY` * 0.1)\n"
+ "WHEN NOT MATCHED THEN INSERT (`NAME`, `DEPT`, `SALARY`) "
+ "(VALUES (ROW(`T`.`NAME`, 10, (`T`.`SALARY` * 0.15))))";
@@ -7461,11 +7471,11 @@ public class SqlParserTest {
SqlNode node = getSqlParser("alter system set schema = true").parseStmt();
SqlSetOption opt = (SqlSetOption) node;
assertThat(opt.getScope(), equalTo("SYSTEM"));
- SqlPrettyWriter writer = new SqlPrettyWriter(CalciteSqlDialect.DEFAULT);
+ SqlPrettyWriter writer = new SqlPrettyWriter();
assertThat(writer.format(opt.getName()), equalTo("\"SCHEMA\""));
- writer = new SqlPrettyWriter(CalciteSqlDialect.DEFAULT);
+ writer = new SqlPrettyWriter();
assertThat(writer.format(opt.getValue()), equalTo("TRUE"));
- writer = new SqlPrettyWriter(CalciteSqlDialect.DEFAULT);
+ writer = new SqlPrettyWriter();
assertThat(writer.format(opt),
equalTo("ALTER SYSTEM SET \"SCHEMA\" = TRUE"));
@@ -7493,10 +7503,10 @@ public class SqlParserTest {
node = getSqlParser("reset schema").parseStmt();
opt = (SqlSetOption) node;
assertThat(opt.getScope(), equalTo(null));
- writer = new SqlPrettyWriter(CalciteSqlDialect.DEFAULT);
+ writer = new SqlPrettyWriter();
assertThat(writer.format(opt.getName()), equalTo("\"SCHEMA\""));
assertThat(opt.getValue(), equalTo(null));
- writer = new SqlPrettyWriter(CalciteSqlDialect.DEFAULT);
+ writer = new SqlPrettyWriter();
assertThat(writer.format(opt),
equalTo("RESET \"SCHEMA\""));
@@ -8716,7 +8726,7 @@ public class SqlParserTest {
final String expected = "UPDATE `EMPS`\n"
+ "/*+ `PROPERTIES`(`K1` ='v1', `K2` ='v2'), "
+ "`INDEX`(`IDX1`, `IDX2`), `NO_HASH_JOIN` */ "
- + "SET `EMPNO` = (`EMPNO` + 1)\n"
+ + "SET `EMPNO` = (`EMPNO` + 1)"
+ ", `SAL` = (`SAL` - 1)\n"
+ "WHERE (`EMPNO` = 12)";
sql(sql).ok(expected);
@@ -8737,8 +8747,8 @@ public class SqlParserTest {
+ "AS `E`\n"
+ "USING `TEMPEMPS` AS `T`\n"
+ "ON (`E`.`EMPNO` = `T`.`EMPNO`)\n"
- + "WHEN MATCHED THEN UPDATE SET `NAME` = `T`.`NAME`\n"
- + ", `DEPTNO` = `T`.`DEPTNO`\n"
+ + "WHEN MATCHED THEN UPDATE SET `NAME` = `T`.`NAME`"
+ + ", `DEPTNO` = `T`.`DEPTNO`"
+ ", `SALARY` = (`T`.`SALARY` * 0.1)\n"
+ "WHEN NOT MATCHED THEN INSERT (`NAME`, `DEPT`, `SALARY`) "
+ "(VALUES (ROW(`T`.`NAME`, 10, (`T`.`SALARY` * 0.15))))";
@@ -8796,8 +8806,9 @@ public class SqlParserTest {
SqlNode sqlNode,
SqlDialect dialect,
String expected) {
- // no dialect, always parenthesize
- final String actual = sqlNode.toSqlString(dialect, true).getSql();
+ final SqlDialect dialect2 = Util.first(dialect, AnsiSqlDialect.DEFAULT);
+ final SqlWriterConfig c2 = SQL_WRITER_CONFIG.withDialect(dialect2);
+ final String actual = sqlNode.toSqlString(c -> c2).getSql();
TestUtil.assertEqualsVerbose(expected, linux(actual));
}
@@ -8933,21 +8944,64 @@ public class SqlParserTest {
* unparsing a query are consistent with the original query.
*/
public class UnparsingTesterImpl extends TesterImpl {
+ private UnaryOperator<SqlWriterConfig> simple() {
+ return c -> c.withSelectListItemsOnSeparateLines(false)
+ .withUpdateSetListNewline(false)
+ .withIndentation(0)
+ .withFromFolding(SqlWriterConfig.LineFolding.TALL);
+ }
+
+ private UnaryOperator<SqlWriterConfig> simpleWithParens() {
+ return simple().andThen(withParens())::apply;
+ }
+
+ private UnaryOperator<SqlWriterConfig> simpleWithParensAnsi() {
+ return simpleWithParens().andThen(withAnsi())::apply;
+ }
+
+ private UnaryOperator<SqlWriterConfig> withParens() {
+ return c -> c.withAlwaysUseParentheses(true);
+ }
+
+ private UnaryOperator<SqlWriterConfig> withAnsi() {
+ return c -> c.withDialect(AnsiSqlDialect.DEFAULT);
+ }
+
+ private UnaryOperator<SqlWriterConfig> randomize(Random random) {
+ return c -> c.withFoldLength(random.nextInt(5) * 20 + 3)
+ .withHavingFolding(nextLineFolding(random))
+ .withWhereFolding(nextLineFolding(random))
+ .withSelectFolding(nextLineFolding(random))
+ .withFromFolding(nextLineFolding(random))
+ .withGroupByFolding(nextLineFolding(random))
+ .withClauseStartsLine(random.nextBoolean())
+ .withClauseEndsLine(random.nextBoolean());
+ }
+
+ private String toSqlString(SqlNodeList sqlNodeList,
+ UnaryOperator<SqlWriterConfig> transform) {
+ return sqlNodeList.getList().stream()
+ .map(node -> node.toSqlString(transform).getSql())
+ .collect(Collectors.joining(";"));
+ }
- private String toSqlString(SqlNodeList sqlNodeList) {
- List<String> sqls = sqlNodeList.getList().stream()
- .map(it -> it.toSqlString(CalciteSqlDialect.DEFAULT, false).getSql())
- .collect(Collectors.toList());
- return String.join(";", sqls);
+ private SqlWriterConfig.LineFolding nextLineFolding(Random random) {
+ return nextEnum(random, SqlWriterConfig.LineFolding.class);
+ }
+
+ private <E extends Enum<E>> E nextEnum(Random random, Class<E> enumClass) {
+ final E[] constants = enumClass.getEnumConstants();
+ return constants[random.nextInt(constants.length)];
}
private void checkList(SqlNodeList sqlNodeList, List<String> expected) {
- assertEquals(expected.size(), sqlNodeList.size());
+ assertThat(sqlNodeList.size(), is(expected.size()));
for (int i = 0; i < sqlNodeList.size(); i++) {
SqlNode sqlNode = sqlNodeList.get(i);
// Unparse with no dialect, always parenthesize.
- final String actual = sqlNode.toSqlString(null, true).getSql();
+ final String actual =
+ sqlNode.toSqlString(simpleWithParensAnsi()).getSql();
assertEquals(expected.get(i), linux(actual));
}
}
@@ -8959,7 +9013,7 @@ public class SqlParserTest {
// Unparse again in Calcite dialect (which we can parse), and
// minimal parentheses.
- final String sql1 = toSqlString(sqlNodeList);
+ final String sql1 = toSqlString(sqlNodeList, simple());
// Parse and unparse again.
SqlNodeList sqlNodeList2;
@@ -8970,7 +9024,7 @@ public class SqlParserTest {
} finally {
quoting = q;
}
- final String sql2 = toSqlString(sqlNodeList2);
+ final String sql2 = toSqlString(sqlNodeList2, simple());
// Should be the same as we started with.
assertEquals(sql1, sql2);
@@ -8979,6 +9033,10 @@ public class SqlParserTest {
// If the unparser is not including sufficient parens to override
// precedence, the problem will show up here.
checkList(sqlNodeList2, expected);
+
+ final Random random = new Random();
+ final String sql3 = toSqlString(sqlNodeList, randomize(random));
+ assertThat(sql3, notNullValue());
}
@Override public void check(String sql, SqlDialect dialect, String expected,
@@ -8988,13 +9046,15 @@ public class SqlParserTest {
parserChecker);
// Unparse with the given dialect, always parenthesize.
- final String actual = sqlNode.toSqlString(dialect, true).getSql();
+ final SqlDialect dialect2 = Util.first(dialect, AnsiSqlDialect.DEFAULT);
+ final UnaryOperator<SqlWriterConfig> transform =
+ simpleWithParens().andThen(c -> c.withDialect(dialect2))::apply;
+ final String actual = sqlNode.toSqlString(transform).getSql();
assertEquals(expected, linux(actual));
// Unparse again in Calcite dialect (which we can parse), and
// minimal parentheses.
- final String sql1 =
- sqlNode.toSqlString(CalciteSqlDialect.DEFAULT, false).getSql();
+ final String sql1 = sqlNode.toSqlString(simple()).getSql();
// Parse and unparse again.
SqlNode sqlNode2;
@@ -9005,8 +9065,7 @@ public class SqlParserTest {
} finally {
quoting = q;
}
- final String sql2 =
- sqlNode2.toSqlString(CalciteSqlDialect.DEFAULT, false).getSql();
+ final String sql2 = sqlNode2.toSqlString(simple()).getSql();
// Should be the same as we started with.
assertEquals(sql1, sql2);
@@ -9014,8 +9073,23 @@ public class SqlParserTest {
// Now unparse again in the given dialect.
// If the unparser is not including sufficient parens to override
// precedence, the problem will show up here.
- final String actual2 = sqlNode2.toSqlString(dialect, true).getSql();
+ final String actual2 = sqlNode.toSqlString(transform).getSql();
assertEquals(expected, linux(actual2));
+
+ // Now unparse with a randomly configured SqlPrettyWriter.
+ // (This is a much a test for SqlPrettyWriter as for the parser.)
+ final Random random = new Random();
+ final String sql3 = sqlNode.toSqlString(randomize(random)).getSql();
+ assertThat(sql3, notNullValue());
+ SqlNode sqlNode4;
+ try {
+ quoting = Quoting.DOUBLE_QUOTE;
+ sqlNode4 = parseStmtAndHandleEx(sql1, b -> b, parser -> { });
+ } finally {
+ quoting = q;
+ }
+ final String sql4 = sqlNode4.toSqlString(simple()).getSql();
+ assertEquals(sql1, sql4);
}
@Override public void checkExp(String sql, String expected,
@@ -9023,13 +9097,15 @@ public class SqlParserTest {
SqlNode sqlNode = parseExpressionAndHandleEx(sql, parserChecker);
// Unparse with no dialect, always parenthesize.
- final String actual = sqlNode.toSqlString(null, true).getSql();
+ final UnaryOperator<SqlWriterConfig> transform = c ->
+ simpleWithParens().apply(c).withDialect(AnsiSqlDialect.DEFAULT);
+ final String actual = sqlNode.toSqlString(transform).getSql();
assertEquals(expected, linux(actual));
// Unparse again in Calcite dialect (which we can parse), and
// minimal parentheses.
final String sql1 =
- sqlNode.toSqlString(CalciteSqlDialect.DEFAULT, false).getSql();
+ sqlNode.toSqlString(UnaryOperator.identity()).getSql();
// Parse and unparse again.
// (Turn off parser checking, and use double-quotes.)
@@ -9042,7 +9118,7 @@ public class SqlParserTest {
quoting = q;
}
final String sql2 =
- sqlNode2.toSqlString(CalciteSqlDialect.DEFAULT, false).getSql();
+ sqlNode2.toSqlString(UnaryOperator.identity()).getSql();
// Should be the same as we started with.
assertEquals(sql1, sql2);
diff --git a/core/src/test/java/org/apache/calcite/sql/test/SqlOperatorBaseTest.java b/core/src/test/java/org/apache/calcite/sql/test/SqlOperatorBaseTest.java
index 6559238..4300ba4 100644
--- a/core/src/test/java/org/apache/calcite/sql/test/SqlOperatorBaseTest.java
+++ b/core/src/test/java/org/apache/calcite/sql/test/SqlOperatorBaseTest.java
@@ -36,7 +36,6 @@ import org.apache.calcite.sql.SqlNodeList;
import org.apache.calcite.sql.SqlOperandCountRange;
import org.apache.calcite.sql.SqlOperator;
import org.apache.calcite.sql.dialect.AnsiSqlDialect;
-import org.apache.calcite.sql.dialect.CalciteSqlDialect;
import org.apache.calcite.sql.fun.SqlLibrary;
import org.apache.calcite.sql.fun.SqlLibraryOperatorTableFactory;
import org.apache.calcite.sql.fun.SqlLibraryOperators;
@@ -9004,8 +9003,7 @@ public abstract class SqlOperatorBaseTest {
if (!typeChecker.checkOperandTypes(binding, false)) {
continue;
}
- final SqlPrettyWriter writer =
- new SqlPrettyWriter(CalciteSqlDialect.DEFAULT);
+ final SqlPrettyWriter writer = new SqlPrettyWriter();
op.unparse(writer, call, 0, 0);
final String s = writer.toSqlString().toString();
if (s.startsWith("OVERLAY(")
diff --git a/core/src/test/java/org/apache/calcite/sql/test/SqlPrettyWriterTest.java b/core/src/test/java/org/apache/calcite/sql/test/SqlPrettyWriterTest.java
index 57137b6..ebe61b2 100644
--- a/core/src/test/java/org/apache/calcite/sql/test/SqlPrettyWriterTest.java
+++ b/core/src/test/java/org/apache/calcite/sql/test/SqlPrettyWriterTest.java
@@ -19,6 +19,7 @@ package org.apache.calcite.sql.test;
import org.apache.calcite.sql.SqlCall;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.SqlWriter;
+import org.apache.calcite.sql.SqlWriterConfig;
import org.apache.calcite.sql.dialect.AnsiSqlDialect;
import org.apache.calcite.sql.parser.SqlParseException;
import org.apache.calcite.sql.parser.SqlParser;
@@ -32,7 +33,6 @@ import org.junit.jupiter.api.Test;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Objects;
-import java.util.function.Consumer;
import java.util.function.UnaryOperator;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -69,10 +69,10 @@ public class SqlPrettyWriterTest {
private final boolean expr;
private final String desc;
private final String formatted;
- private final UnaryOperator<SqlPrettyWriter> transform;
+ private final UnaryOperator<SqlWriterConfig> transform;
Sql(String sql, boolean expr, String desc, String formatted,
- UnaryOperator<SqlPrettyWriter> transform) {
+ UnaryOperator<SqlWriterConfig> transform) {
this.sql = Objects.requireNonNull(sql);
this.expr = expr;
this.desc = desc;
@@ -80,13 +80,10 @@ public class SqlPrettyWriterTest {
this.transform = Objects.requireNonNull(transform);
}
- Sql withWriter(Consumer<SqlPrettyWriter> consumer) {
- Objects.requireNonNull(consumer);
- return new Sql(sql, expr, desc, formatted, w -> {
- final SqlPrettyWriter w2 = transform.apply(w);
- consumer.accept(w2);
- return w2;
- });
+ Sql withWriter(UnaryOperator<SqlWriterConfig> transform) {
+ Objects.requireNonNull(transform);
+ return new Sql(sql, expr, desc, formatted, w ->
+ transform.apply(this.transform.apply(w)));
}
Sql expectingDesc(String desc) {
@@ -108,8 +105,10 @@ public class SqlPrettyWriterTest {
}
Sql check() {
- final SqlPrettyWriter prettyWriter =
- transform.apply(new SqlPrettyWriter(AnsiSqlDialect.DEFAULT));
+ final SqlWriterConfig config =
+ transform.apply(SqlPrettyWriter.config()
+ .withDialect(AnsiSqlDialect.DEFAULT));
+ final SqlPrettyWriter prettyWriter = new SqlPrettyWriter(config);
final SqlNode node;
if (expr) {
final SqlCall valuesCall = (SqlCall) parseQuery("VALUES (" + sql + ")");
@@ -169,11 +168,11 @@ public class SqlPrettyWriterTest {
}
private Sql sql(String sql) {
- return new Sql(sql, false, "${desc}", "${formatted}", w -> w);
+ return new Sql(sql, false, null, "${formatted}", w -> w);
}
private Sql expr(String sql) {
- return sql(sql).withExpr(true).expectingDesc(null);
+ return sql(sql).withExpr(true);
}
// ~ Tests ----------------------------------------------------------------
@@ -184,44 +183,113 @@ public class SqlPrettyWriterTest {
@Test public void testIndent8() {
simple()
- .withWriter(w -> w.setIndentation(8))
+ .expectingDesc("${desc}")
+ .withWriter(w -> w.withIndentation(8))
.check();
}
@Test public void testClausesNotOnNewLine() {
simple()
- .withWriter(w -> w.setClauseStartsLine(false))
+ .withWriter(w -> w.withClauseStartsLine(false))
.check();
}
@Test public void testSelectListItemsOnSeparateLines() {
simple()
- .withWriter(w -> w.setSelectListItemsOnSeparateLines(true))
+ .withWriter(w -> w.withSelectListItemsOnSeparateLines(true))
.check();
}
- @Test public void testSelectListExtraIndentFlag() {
+ @Test public void testSelectListNoExtraIndentFlag() {
simple()
- .withWriter(w -> w.setSelectListItemsOnSeparateLines(true))
- .withWriter(w -> w.setSelectListExtraIndentFlag(false))
+ .withWriter(w -> w.withSelectListItemsOnSeparateLines(true)
+ .withSelectListExtraIndentFlag(false)
+ .withClauseEndsLine(true))
+ .check();
+ }
+
+ @Test public void testFold() {
+ simple()
+ .withWriter(w -> w.withLineFolding(SqlWriterConfig.LineFolding.FOLD)
+ .withFoldLength(45))
+ .check();
+ }
+
+ @Test public void testChop() {
+ simple()
+ .withWriter(w -> w.withLineFolding(SqlWriterConfig.LineFolding.CHOP)
+ .withFoldLength(45))
+ .check();
+ }
+
+ @Test public void testChopLeadingComma() {
+ simple()
+ .withWriter(w -> w.withLineFolding(SqlWriterConfig.LineFolding.CHOP)
+ .withFoldLength(45)
+ .withLeadingComma(true))
+ .check();
+ }
+
+ @Test public void testLeadingComma() {
+ simple()
+ .withWriter(w -> w.withLeadingComma(true)
+ .withSelectListItemsOnSeparateLines(true)
+ .withSelectListExtraIndentFlag(true))
+ .check();
+ }
+
+ @Test public void testClauseEndsLine() {
+ simple()
+ .withWriter(w -> w.withClauseEndsLine(true)
+ .withLineFolding(SqlWriterConfig.LineFolding.WIDE)
+ .withFoldLength(45))
+ .check();
+ }
+
+ @Test public void testClauseEndsLineTall() {
+ simple()
+ .withWriter(w -> w.withClauseEndsLine(true)
+ .withLineFolding(SqlWriterConfig.LineFolding.TALL)
+ .withFoldLength(45))
+ .check();
+ }
+
+ @Test public void testClauseEndsLineFold() {
+ simple()
+ .withWriter(w -> w.withClauseEndsLine(true)
+ .withLineFolding(SqlWriterConfig.LineFolding.FOLD)
+ .withFoldLength(45))
+ .check();
+ }
+
+ /** Tests formatting a query with Looker's preferences. */
+ @Test public void testLooker() {
+ simple()
+ .withWriter(w -> w.withFoldLength(60)
+ .withLineFolding(SqlWriterConfig.LineFolding.STEP)
+ .withSelectFolding(SqlWriterConfig.LineFolding.TALL)
+ .withFromFolding(SqlWriterConfig.LineFolding.TALL)
+ .withWhereFolding(SqlWriterConfig.LineFolding.TALL)
+ .withHavingFolding(SqlWriterConfig.LineFolding.TALL)
+ .withClauseEndsLine(true))
.check();
}
@Test public void testKeywordsLowerCase() {
simple()
- .withWriter(w -> w.setKeywordsLowerCase(true))
+ .withWriter(w -> w.withKeywordsLowerCase(true))
.check();
}
@Test public void testParenthesizeAllExprs() {
simple()
- .withWriter(w -> w.setAlwaysUseParentheses(true))
+ .withWriter(w -> w.withAlwaysUseParentheses(true))
.check();
}
@Test public void testOnlyQuoteIdentifiersWhichNeedIt() {
simple()
- .withWriter(w -> w.setQuoteAllIdentifiers(false))
+ .withWriter(w -> w.withQuoteAllIdentifiers(false))
.check();
}
@@ -229,7 +297,27 @@ public class SqlPrettyWriterTest {
// Note that ( is at the indent, SELECT is on the same line, and ) is
// below it.
simple()
- .withWriter(w -> w.setSubQueryStyle(SqlWriter.SubQueryStyle.BLACK))
+ .withWriter(w -> w.withSubQueryStyle(SqlWriter.SubQueryStyle.BLACK))
+ .check();
+ }
+
+ @Test public void testBlackSubQueryStyleIndent0() {
+ simple()
+ .withWriter(w -> w.withSubQueryStyle(SqlWriter.SubQueryStyle.BLACK)
+ .withIndentation(0))
+ .check();
+ }
+
+ @Test public void testValuesNewline() {
+ sql("select * from (values (1, 2), (3, 4)) as t")
+ .withWriter(w -> w.withValuesListNewline(true))
+ .check();
+ }
+
+ @Test public void testValuesLeadingCommas() {
+ sql("select * from (values (1, 2), (3, 4)) as t")
+ .withWriter(w -> w.withValuesListNewline(true)
+ .withLeadingComma(true))
.check();
}
@@ -260,7 +348,7 @@ public class SqlPrettyWriterTest {
+ "ELSE 7\n"
+ "END";
expr(sql)
- .withWriter(w -> w.setCaseClausesOnNewLines(true))
+ .withWriter(w -> w.withCaseClausesOnNewLines(true))
.expectingFormatted(formatted)
.check();
}
@@ -310,10 +398,6 @@ public class SqlPrettyWriterTest {
}
@Test public void testUnion() {
- // todo: SELECT should not be indented from UNION, like this:
- // UNION
- // SELECT *
- // FROM `W`
final String sql = "select * from t "
+ "union select * from ("
+ " select * from u "
@@ -321,19 +405,50 @@ public class SqlPrettyWriterTest {
+ "union select * from w "
+ "order by a, b";
sql(sql)
- .expectingDesc(null)
.check();
}
@Test public void testMultiset() {
sql("values (multiset (select * from t))")
- .expectingDesc(null)
.check();
}
+ @Test public void testJoinComma() {
+ final String sql = "select *\n"
+ + "from x, y as y1, z, (select * from a, a2 as a3),\n"
+ + " (select * from b) as b2\n"
+ + "where p = q\n"
+ + "and exists (select 1 from v, w)";
+ sql(sql).check();
+ }
+
@Test public void testInnerJoin() {
sql("select * from x inner join y on x.k=y.k")
- .expectingDesc(null)
+ .check();
+ }
+
+ @Test public void testJoinTall() {
+ sql("select * from x inner join y on x.k=y.k left join z using (a)")
+ .withWriter(c -> c.withLineFolding(SqlWriterConfig.LineFolding.TALL))
+ .check();
+ }
+
+ @Test public void testJoinTallClauseEndsLine() {
+ sql("select * from x inner join y on x.k=y.k left join z using (a)")
+ .withWriter(c -> c.withLineFolding(SqlWriterConfig.LineFolding.TALL)
+ .withClauseEndsLine(true))
+ .check();
+ }
+
+ @Test public void testJoinLateralSubQueryTall() {
+ final String sql = "select *\n"
+ + "from (select a from customers where b < c group by d) as c,\n"
+ + " products,\n"
+ + " lateral (select e from orders where exists (\n"
+ + " select 1 from promotions)) as t5\n"
+ + "group by f";
+ sql(sql)
+ .withWriter(c -> c.withLineFolding(SqlWriterConfig.LineFolding.TALL))
.check();
}
@@ -344,9 +459,9 @@ public class SqlPrettyWriterTest {
+ " or ((a or b) is true) and d not in (f,g)"
+ " or x <> z";
sql(sql)
- .withWriter(w -> w.setSelectListItemsOnSeparateLines(true))
- .withWriter(w -> w.setSelectListExtraIndentFlag(false))
- .withWriter(w -> w.setWhereListItemsOnSeparateLines(true))
+ .withWriter(w -> w.withSelectListItemsOnSeparateLines(true)
+ .withSelectListExtraIndentFlag(false)
+ .withWhereListItemsOnSeparateLines(true))
.check();
}
@@ -357,9 +472,80 @@ public class SqlPrettyWriterTest {
+ " or ((a or b) is true)) and (d not in (f,g)"
+ " or v <> ((w * x) + y) * z)";
sql(sql)
- .withWriter(w -> w.setSelectListItemsOnSeparateLines(true))
- .withWriter(w -> w.setSelectListExtraIndentFlag(false))
- .withWriter(w -> w.setWhereListItemsOnSeparateLines(true))
+ .withWriter(w -> w.withSelectListItemsOnSeparateLines(true)
+ .withSelectListExtraIndentFlag(false)
+ .withWhereListItemsOnSeparateLines(true))
+ .check();
+ }
+
+ /** As {@link #testWhereListItemsOnSeparateLinesAnd()}, but
+ * with {@link SqlWriterConfig#clauseEndsLine ClauseEndsLine=true}. */
+ @Test public void testWhereListItemsOnSeparateLinesAndNewline() {
+ final String sql = "select x"
+ + " from y"
+ + " where h is not null and (i < j"
+ + " or ((a or b) is true)) and (d not in (f,g)"
+ + " or v <> ((w * x) + y) * z)";
+ sql(sql)
+ .withWriter(w -> w.withSelectListItemsOnSeparateLines(true)
+ .withSelectListExtraIndentFlag(false)
+ .withWhereListItemsOnSeparateLines(true)
+ .withClauseEndsLine(true))
+ .check();
+ }
+
+ @Test public void testUpdate() {
+ final String sql = "update emp\n"
+ + "set mgr = mgr + 1, deptno = 5\n"
+ + "where deptno = 10 and name = 'Fred'";
+ sql(sql)
.check();
}
+
+ @Test public void testUpdateNoLine() {
+ final String sql = "update emp\n"
+ + "set mgr = mgr + 1, deptno = 5\n"
+ + "where deptno = 10 and name = 'Fred'";
+ sql(sql)
+ .withWriter(w -> w.withUpdateSetListNewline(false))
+ .check();
+ }
+
+ @Test public void testUpdateNoLine2() {
+ final String sql = "update emp\n"
+ + "set mgr = mgr + 1, deptno = 5\n"
+ + "where deptno = 10 and name = 'Fred'";
+ sql(sql)
+ .withWriter(w -> w.withUpdateSetListNewline(false)
+ .withClauseStartsLine(false))
+ .check();
+ }
+
+ public static void main(String[] args) throws SqlParseException {
+ final String sql = "select x as a, b as b, c as c, d,"
+ + " 'mixed-Case string',"
+ + " unquotedCamelCaseId,"
+ + " \"quoted id\" "
+ + "from"
+ + " (select *"
+ + " from t"
+ + " where x = y and a > 5"
+ + " group by z, zz"
+ + " window w as (partition by c),"
+ + " w1 as (partition by c,d order by a, b"
+ + " range between interval '2:2' hour to minute preceding"
+ + " and interval '1' day following)) "
+ + "order by gg desc nulls last, hh asc";
+ final SqlNode node = SqlParser.create(sql).parseQuery();
+
+ final SqlWriterConfig config = SqlPrettyWriter.config()
+ .withLineFolding(SqlWriterConfig.LineFolding.STEP)
+ .withSelectFolding(SqlWriterConfig.LineFolding.TALL)
+ .withFromFolding(SqlWriterConfig.LineFolding.TALL)
+ .withWhereFolding(SqlWriterConfig.LineFolding.TALL)
+ .withHavingFolding(SqlWriterConfig.LineFolding.TALL)
+ .withIndentation(4)
+ .withClauseEndsLine(true);
+ System.out.println(new SqlPrettyWriter(config).format(node));
+ }
}
diff --git a/core/src/test/java/org/apache/calcite/test/JdbcAdapterTest.java b/core/src/test/java/org/apache/calcite/test/JdbcAdapterTest.java
index e4336fc..25f0c0c 100644
--- a/core/src/test/java/org/apache/calcite/test/JdbcAdapterTest.java
+++ b/core/src/test/java/org/apache/calcite/test/JdbcAdapterTest.java
@@ -306,7 +306,7 @@ public class JdbcAdapterTest {
+ "\"t0\".\"DNAME\", \"SALGRADE\".\"GRADE\"\n"
+ "FROM \"SCOTT\".\"SALGRADE\"\n"
+ "INNER JOIN ((SELECT \"EMPNO\", \"ENAME\", \"SAL\", \"DEPTNO\"\n"
- + "FROM \"SCOTT\".\"EMP\") AS \"t\"\n"
+ + "FROM \"SCOTT\".\"EMP\") AS \"t\" "
+ "INNER JOIN (SELECT \"DEPTNO\", \"DNAME\"\n"
+ "FROM \"SCOTT\".\"DEPT\") AS \"t0\" ON \"t\".\"DEPTNO\" = \"t0\".\"DEPTNO\")"
+ " ON \"SALGRADE\".\"LOSAL\" < \"t\".\"SAL\" AND \"SALGRADE\".\"HISAL\" > \"t\".\"SAL\"");
diff --git a/core/src/test/resources/org/apache/calcite/sql/test/SqlPrettyWriterTest.xml b/core/src/test/resources/org/apache/calcite/sql/test/SqlPrettyWriterTest.xml
index 3b81a8c..84a38b6 100644
--- a/core/src/test/resources/org/apache/calcite/sql/test/SqlPrettyWriterTest.xml
+++ b/core/src/test/resources/org/apache/calcite/sql/test/SqlPrettyWriterTest.xml
@@ -16,8 +16,20 @@
~ limitations under the License.
-->
<Root>
+ <TestCase name="testBlackSubQueryStyleIndent0">
+ <Resource name="formatted">
+ <![CDATA[SELECT `X` AS `A`, `B` AS `B`, `C` AS `C`, `D`, 'mixed-Case string', `UNQUOTEDCAMELCASEID`, `quoted id`
+FROM
+(SELECT *
+FROM `T`
+WHERE `X` = `Y` AND `A` > 5
+GROUP BY `Z`, `ZZ`
+WINDOW `W` AS (PARTITION BY `C`),
+`W1` AS (PARTITION BY `C`, `D` ORDER BY `A`, `B` RANGE BETWEEN INTERVAL '2:2' HOUR TO MINUTE PRECEDING AND INTERVAL '1' DAY FOLLOWING))
+ORDER BY `GG`]]>
+ </Resource>
+ </TestCase>
<TestCase name="testDefault">
- <Resource name="desc"/>
<Resource name="formatted">
<![CDATA[SELECT `X` AS `A`, `B` AS `B`, `C` AS `C`, `D`, 'mixed-Case string', `UNQUOTEDCAMELCASEID`, `quoted id`
FROM (SELECT *
@@ -44,6 +56,105 @@ ELSE 7
END]]>
</Resource>
</TestCase>
+ <TestCase name="testChop">
+ <Resource name="formatted">
+ <![CDATA[SELECT `X` AS `A`,
+ `B` AS `B`,
+ `C` AS `C`,
+ `D`,
+ 'mixed-Case string',
+ `UNQUOTEDCAMELCASEID`,
+ `quoted id`
+FROM (SELECT *
+ FROM `T`
+ WHERE `X` = `Y` AND `A` > 5
+ GROUP BY `Z`, `ZZ`
+ WINDOW `W` AS (PARTITION BY `C`),
+ `W1` AS (PARTITION BY `C`, `D` ORDER BY `A`, `B` RANGE BETWEEN INTERVAL '2:2' HOUR TO MINUTE PRECEDING AND INTERVAL '1' DAY FOLLOWING))
+ORDER BY `GG`]]>
+ </Resource>
+ </TestCase>
+ <TestCase name="testChopLeadingComma">
+ <Resource name="formatted">
+ <![CDATA[SELECT `X` AS `A`
+ , `B` AS `B`
+ , `C` AS `C`
+ , `D`
+ , 'mixed-Case string'
+ , `UNQUOTEDCAMELCASEID`
+ , `quoted id`
+FROM (SELECT *
+ FROM `T`
+ WHERE `X` = `Y` AND `A` > 5
+ GROUP BY `Z`, `ZZ`
+ WINDOW `W` AS (PARTITION BY `C`)
+ , `W1` AS (PARTITION BY `C`, `D` ORDER BY `A`, `B` RANGE BETWEEN INTERVAL '2:2' HOUR TO MINUTE PRECEDING AND INTERVAL '1' DAY FOLLOWING))
+ORDER BY `GG`]]>
+ </Resource>
+ </TestCase>
+ <TestCase name="testClauseEndsLine">
+ <Resource name="formatted">
+ <![CDATA[SELECT `X` AS `A`, `B` AS `B`, `C` AS `C`, `D`, 'mixed-Case string', `UNQUOTEDCAMELCASEID`, `quoted id`
+FROM
+ (SELECT *
+ FROM
+ `T`
+ WHERE `X` = `Y` AND `A` > 5
+ GROUP BY `Z`, `ZZ`
+ WINDOW `W` AS (PARTITION BY `C`), `W1` AS (PARTITION BY `C`, `D` ORDER BY `A`, `B` RANGE BETWEEN INTERVAL '2:2' HOUR TO MINUTE PRECEDING AND INTERVAL '1' DAY FOLLOWING))
+ORDER BY `GG`]]>
+ </Resource>
+ </TestCase>
+ <TestCase name="testClauseEndsLineFold">
+ <Resource name="formatted">
+ <![CDATA[SELECT
+ `X` AS `A`, `B` AS `B`, `C` AS `C`, `D`,
+ 'mixed-Case string',
+ `UNQUOTEDCAMELCASEID`, `quoted id`
+FROM
+ (SELECT *
+ FROM
+ `T`
+ WHERE `X` = `Y` AND `A` > 5
+ GROUP BY `Z`, `ZZ`
+ WINDOW
+ `W` AS (PARTITION BY `C`),
+ `W1` AS (PARTITION BY `C`, `D` ORDER BY `A`, `B` RANGE BETWEEN INTERVAL '2:2' HOUR TO MINUTE PRECEDING AND INTERVAL '1' DAY FOLLOWING))
+ORDER BY `GG`]]>
+ </Resource>
+ </TestCase>
+ <TestCase name="testClauseEndsLineTall">
+ <Resource name="formatted">
+ <![CDATA[SELECT
+ `X` AS `A`,
+ `B` AS `B`,
+ `C` AS `C`,
+ `D`,
+ 'mixed-Case string',
+ `UNQUOTEDCAMELCASEID`,
+ `quoted id`
+FROM
+ (SELECT
+ *
+ FROM
+ `T`
+ WHERE
+ `X` = `Y`
+ AND `A` > 5
+ GROUP BY
+ `Z`,
+ `ZZ`
+ WINDOW
+ `W` AS (
+ PARTITION BY `C`),
+ `W1` AS (
+ PARTITION BY `C`, `D`
+ ORDER BY `A`, `B`
+ RANGE BETWEEN INTERVAL '2:2' HOUR TO MINUTE PRECEDING AND INTERVAL '1' DAY FOLLOWING))
+ORDER BY
+ `GG`]]>
+ </Resource>
+ </TestCase>
<TestCase name="testIndent8">
<Resource name="desc">
<![CDATA[indentation=8]]>
@@ -60,39 +171,45 @@ ORDER BY `GG`]]>
</Resource>
</TestCase>
<TestCase name="testClausesNotOnNewLine">
- <Resource name="desc"/>
<Resource name="formatted">
- <![CDATA[SELECT `X` AS `A`, `B` AS `B`, `C` AS `C`, `D`, 'mixed-Case string', `UNQUOTEDCAMELCASEID`, `quoted id` FROM (SELECT *FROM `T` WHERE `X` = `Y` AND `A` > 5 GROUP BY `Z`, `ZZ` WINDOW `W` AS (PARTITION BY `C`),
+ <![CDATA[SELECT `X` AS `A`, `B` AS `B`, `C` AS `C`, `D`, 'mixed-Case string', `UNQUOTEDCAMELCASEID`, `quoted id` FROM (SELECT *FROM `T` WHERE `X` = `Y` AND `A` > 5 GROUP BY `Z`, `ZZ` WINDOW `W` AS (PARTITION BY `C`), `W1` AS (PARTITION BY `C`, `D` ORDER BY `A`, `B` RANGE BETWEEN INTERVAL '2:2' HOUR TO MINUTE PRECEDING AND INTERVAL '1' DAY FOLLOWING))
+ORDER BY `GG`]]>
+ </Resource>
+ </TestCase>
+ <TestCase name="testFold">
+ <Resource name="formatted">
+ <![CDATA[SELECT `X` AS `A`, `B` AS `B`, `C` AS `C`,
+ `D`, 'mixed-Case string',
+ `UNQUOTEDCAMELCASEID`, `quoted id`
+FROM (SELECT *
+ FROM `T`
+ WHERE `X` = `Y` AND `A` > 5
+ GROUP BY `Z`, `ZZ`
+ WINDOW `W` AS (PARTITION BY `C`),
`W1` AS (PARTITION BY `C`, `D` ORDER BY `A`, `B` RANGE BETWEEN INTERVAL '2:2' HOUR TO MINUTE PRECEDING AND INTERVAL '1' DAY FOLLOWING))
ORDER BY `GG`]]>
</Resource>
</TestCase>
<TestCase name="testSelectListItemsOnSeparateLines">
- <Resource name="desc"/>
<Resource name="formatted">
- <![CDATA[SELECT
- `X` AS `A`,
- `B` AS `B`,
- `C` AS `C`,
+ <![CDATA[SELECT `X` AS `A`,
+ `B` AS `B`,
+ `C` AS `C`,
`D`,
'mixed-Case string',
`UNQUOTEDCAMELCASEID`,
`quoted id`
-FROM (SELECT
- *
+FROM (SELECT *
FROM `T`
WHERE `X` = `Y` AND `A` > 5
- GROUP BY
- `Z`,
+ GROUP BY `Z`,
`ZZ`
WINDOW `W` AS (PARTITION BY `C`),
`W1` AS (PARTITION BY `C`, `D` ORDER BY `A`, `B` RANGE BETWEEN INTERVAL '2:2' HOUR TO MINUTE PRECEDING AND INTERVAL '1' DAY FOLLOWING))
-ORDER BY
-`GG`]]>
+ORDER BY `GG`]]>
</Resource>
</TestCase>
- <TestCase name="testSelectListExtraIndentFlag">
- <Resource name="desc"/>
+ <TestCase name="testSelectListNoExtraIndentFlag">
<Resource name="formatted">
<![CDATA[SELECT
`X` AS `A`,
@@ -102,21 +219,23 @@ ORDER BY
'mixed-Case string',
`UNQUOTEDCAMELCASEID`,
`quoted id`
-FROM (SELECT
+FROM
+ (SELECT
*
- FROM `T`
+ FROM
+ `T`
WHERE `X` = `Y` AND `A` > 5
GROUP BY
`Z`,
`ZZ`
- WINDOW `W` AS (PARTITION BY `C`),
+ WINDOW
+ `W` AS (PARTITION BY `C`),
`W1` AS (PARTITION BY `C`, `D` ORDER BY `A`, `B` RANGE BETWEEN INTERVAL '2:2' HOUR TO MINUTE PRECEDING AND INTERVAL '1' DAY FOLLOWING))
ORDER BY
-`GG`]]>
+ `GG`]]>
</Resource>
</TestCase>
<TestCase name="testKeywordsLowerCase">
- <Resource name="desc"/>
<Resource name="formatted">
<![CDATA[select `X` as `A`, `B` as `B`, `C` as `C`, `D`, 'mixed-Case string', `UNQUOTEDCAMELCASEID`, `quoted id`
from (select *
@@ -129,7 +248,6 @@ order by `GG`]]>
</Resource>
</TestCase>
<TestCase name="testParenthesizeAllExprs">
- <Resource name="desc"/>
<Resource name="formatted">
<![CDATA[SELECT `X` AS `A`, `B` AS `B`, `C` AS `C`, `D`, 'mixed-Case string', `UNQUOTEDCAMELCASEID`, `quoted id`
FROM (SELECT *
@@ -142,7 +260,6 @@ ORDER BY `GG`]]>
</Resource>
</TestCase>
<TestCase name="testOnlyQuoteIdentifiersWhichNeedIt">
- <Resource name="desc"/>
<Resource name="formatted">
<![CDATA[SELECT X AS A, B AS B, C AS C, D, 'mixed-Case string', UNQUOTEDCAMELCASEID, `quoted id`
FROM (SELECT *
@@ -155,10 +272,10 @@ ORDER BY GG]]>
</Resource>
</TestCase>
<TestCase name="testBlackSubQueryStyle">
- <Resource name="desc"/>
<Resource name="formatted">
<![CDATA[SELECT `X` AS `A`, `B` AS `B`, `C` AS `C`, `D`, 'mixed-Case string', `UNQUOTEDCAMELCASEID`, `quoted id`
-FROM ( SELECT *
+FROM
+( SELECT *
FROM `T`
WHERE `X` = `Y` AND `A` > 5
GROUP BY `Z`, `ZZ`
@@ -182,6 +299,74 @@ ORDER BY `GG`]]>
<![CDATA[CAST(`X` + `Y` AS DECIMAL(5, 10))]]>
</Resource>
</TestCase>
+ <TestCase name="testJoinComma">
+ <Resource name="formatted">
+ <![CDATA[SELECT *
+FROM `X`,
+ `Y` AS `Y1`,
+ `Z`,
+ (SELECT *
+ FROM `A`,
+ `A2` AS `A3`),
+ (SELECT *
+ FROM `B`) AS `B2`
+WHERE `P` = `Q` AND EXISTS (SELECT 1
+ FROM `V`,
+ `W`)]]>
+ </Resource>
+ </TestCase>
+ <TestCase name="testJoinLateralSubQueryTall">
+ <Resource name="formatted">
+ <![CDATA[SELECT *
+FROM (SELECT `A`
+ FROM `CUSTOMERS`
+ WHERE `B` < `C`
+ GROUP BY `D`) AS `C`,
+ `PRODUCTS`,
+ LATERAL (SELECT `E`
+ FROM `ORDERS`
+ WHERE EXISTS (SELECT 1
+ FROM `PROMOTIONS`)) AS `T5`
+GROUP BY `F`]]>
+ </Resource>
+ </TestCase>
+ <TestCase name="testJoinTall">
+ <Resource name="formatted">
+ <![CDATA[SELECT *
+FROM `X`
+ INNER JOIN `Y` ON `X`.`K` = `Y`.`K`
+ LEFT JOIN `Z` USING (`A`)]]>
+ </Resource>
+ </TestCase>
+ <TestCase name="testJoinTallClauseEndsLine">
+ <Resource name="formatted">
+ <![CDATA[SELECT
+ *
+FROM
+ `X`
+ INNER JOIN `Y` ON `X`.`K` = `Y`.`K`
+ LEFT JOIN `Z` USING (`A`)]]>
+ </Resource>
+ </TestCase>
+ <TestCase name="testLeadingComma">
+ <Resource name="formatted">
+ <![CDATA[SELECT `X` AS `A`
+ , `B` AS `B`
+ , `C` AS `C`
+ , `D`
+ , 'mixed-Case string'
+ , `UNQUOTEDCAMELCASEID`
+ , `quoted id`
+FROM (SELECT *
+ FROM `T`
+ WHERE `X` = `Y` AND `A` > 5
+ GROUP BY `Z`
+ , `ZZ`
+ WINDOW `W` AS (PARTITION BY `C`)
+ , `W1` AS (PARTITION BY `C`, `D` ORDER BY `A`, `B` RANGE BETWEEN INTERVAL '2:2' HOUR TO MINUTE PRECEDING AND INTERVAL '1' DAY FOLLOWING))
+ORDER BY `GG`]]>
+ </Resource>
+ </TestCase>
<TestCase name="testLiteralChain">
<Resource name="formatted">
<![CDATA['x'
@@ -189,25 +374,52 @@ ORDER BY `GG`]]>
'z']]>
</Resource>
</TestCase>
+ <TestCase name="testLooker">
+ <Resource name="formatted">
+ <![CDATA[SELECT
+ `X` AS `A`,
+ `B` AS `B`,
+ `C` AS `C`,
+ `D`,
+ 'mixed-Case string',
+ `UNQUOTEDCAMELCASEID`,
+ `quoted id`
+FROM
+ (SELECT
+ *
+ FROM
+ `T`
+ WHERE
+ `X` = `Y`
+ AND `A` > 5
+ GROUP BY
+ `Z`, `ZZ`
+ WINDOW
+ `W` AS (PARTITION BY `C`),
+ `W1` AS (PARTITION BY `C`, `D` ORDER BY `A`, `B` RANGE BETWEEN INTERVAL '2:2' HOUR TO MINUTE PRECEDING AND INTERVAL '1' DAY FOLLOWING))
+ORDER BY
+ `GG`]]>
+ </Resource>
+ </TestCase>
<TestCase name="testOverlaps">
<Resource name="formatted">
- <![CDATA[(`X`, `XX`) OVERLAPS (`Y`, `YY`) OR `X` IS NOT NULL]]>
+ <![CDATA[PERIOD (`X`, `XX`) OVERLAPS PERIOD (`Y`, `YY`) OR `X` IS NOT NULL]]>
</Resource>
</TestCase>
<TestCase name="testUnion">
<Resource name="formatted">
<![CDATA[SELECT *
- FROM `T`
- UNION
- SELECT *
- FROM (SELECT *
- FROM `U`
- UNION
- SELECT *
- FROM `V`)
+FROM `T`
+UNION
+SELECT *
+FROM (SELECT *
+ FROM `U`
+ UNION
+ SELECT *
+ FROM `V`)
UNION
- SELECT *
- FROM `W`
+SELECT *
+FROM `W`
ORDER BY `A`, `B`]]>
</Resource>
</TestCase>
@@ -224,11 +436,42 @@ FROM `X`
INNER JOIN `Y` ON `X`.`K` = `Y`.`K`]]>
</Resource>
</TestCase>
+ <TestCase name="testUpdate">
+ <Resource name="formatted">
+ <![CDATA[UPDATE `EMP` SET
+ `MGR` = `MGR` + 1,
+ `DEPTNO` = 5
+WHERE `DEPTNO` = 10 AND `NAME` = 'Fred']]>
+ </Resource>
+ </TestCase>
+ <TestCase name="testUpdateNoLine">
+ <Resource name="formatted">
+ <![CDATA[UPDATE `EMP` SET `MGR` = `MGR` + 1, `DEPTNO` = 5
+WHERE `DEPTNO` = 10 AND `NAME` = 'Fred']]>
+ </Resource>
+ </TestCase>
+ <TestCase name="testUpdateNoLine2">
+ <Resource name="formatted">
+ <![CDATA[UPDATE `EMP` SET `MGR` = `MGR` + 1, `DEPTNO` = 5 WHERE `DEPTNO` = 10 AND `NAME` = 'Fred']]>
+ </Resource>
+ </TestCase>
+ <TestCase name="testValuesLeadingCommas">
+ <Resource name="formatted">
+ <![CDATA[SELECT *
+FROM (VALUES ROW(1, 2)
+ , ROW(3, 4)) AS `T`]]>
+ </Resource>
+ </TestCase>
+ <TestCase name="testValuesNewline">
+ <Resource name="formatted">
+ <![CDATA[SELECT *
+FROM (VALUES ROW(1, 2),
+ ROW(3, 4)) AS `T`]]>
+ </Resource>
+ </TestCase>
<TestCase name="testWhereListItemsOnSeparateLinesOr">
- <Resource name="desc"/>
<Resource name="formatted">
- <![CDATA[SELECT
- `X`
+ <![CDATA[SELECT `X`
FROM `Y`
WHERE `H` IS NOT NULL AND `I` < `J`
OR (`A` OR `B`) IS TRUE AND `D` NOT IN (`F`, `G`)
@@ -236,14 +479,24 @@ WHERE `H` IS NOT NULL AND `I` < `J`
</Resource>
</TestCase>
<TestCase name="testWhereListItemsOnSeparateLinesAnd">
- <Resource name="desc"/>
<Resource name="formatted">
- <![CDATA[SELECT
- `X`
+ <![CDATA[SELECT `X`
FROM `Y`
WHERE `H` IS NOT NULL
AND (`I` < `J` OR (`A` OR `B`) IS TRUE)
AND (`D` NOT IN (`F`, `G`) OR `V` <> (`W` * `X` + `Y`) * `Z`)]]>
</Resource>
</TestCase>
+ <TestCase name="testWhereListItemsOnSeparateLinesAndNewline">
+ <Resource name="formatted">
+ <![CDATA[SELECT
+ `X`
+FROM
+ `Y`
+WHERE
+ `H` IS NOT NULL
+ AND (`I` < `J` OR (`A` OR `B`) IS TRUE)
+ AND (`D` NOT IN (`F`, `G`) OR `V` <> (`W` * `X` + `Y`) * `Z`)]]>
+ </Resource>
+ </TestCase>
</Root>
diff --git a/piglet/src/main/java/org/apache/calcite/piglet/PigConverter.java b/piglet/src/main/java/org/apache/calcite/piglet/PigConverter.java
index a7a8e63..2db0bb7 100644
--- a/piglet/src/main/java/org/apache/calcite/piglet/PigConverter.java
+++ b/piglet/src/main/java/org/apache/calcite/piglet/PigConverter.java
@@ -35,6 +35,7 @@ import org.apache.calcite.rel.rules.ProjectWindowTransposeRule;
import org.apache.calcite.sql.SqlDialect;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.SqlWriter;
+import org.apache.calcite.sql.SqlWriterConfig;
import org.apache.calcite.sql.pretty.SqlPrettyWriter;
import org.apache.calcite.tools.FrameworkConfig;
import org.apache.calcite.tools.Program;
@@ -221,11 +222,13 @@ public class PigConverter extends PigServer {
*/
public List<String> pigToSql(String pigQuery, SqlDialect sqlDialect)
throws IOException {
- final SqlPrettyWriter writer = new SqlPrettyWriter(sqlDialect);
- writer.setQuoteAllIdentifiers(false);
- writer.setAlwaysUseParentheses(false);
- writer.setSelectListItemsOnSeparateLines(false);
- writer.setIndentation(2);
+ final SqlWriterConfig config = SqlPrettyWriter.config()
+ .withQuoteAllIdentifiers(false)
+ .withAlwaysUseParentheses(false)
+ .withSelectListItemsOnSeparateLines(false)
+ .withIndentation(2)
+ .withDialect(sqlDialect);
+ final SqlPrettyWriter writer = new SqlPrettyWriter(config);
return pigToSql(pigQuery, writer);
}
diff --git a/piglet/src/test/java/org/apache/calcite/test/PigRelOpTest.java b/piglet/src/test/java/org/apache/calcite/test/PigRelOpTest.java
index 4098989..feb6020 100644
--- a/piglet/src/test/java/org/apache/calcite/test/PigRelOpTest.java
+++ b/piglet/src/test/java/org/apache/calcite/test/PigRelOpTest.java
@@ -253,12 +253,12 @@ public class PigRelOpTest extends PigRelTestBase {
final String sql = ""
+ "SELECT *\n"
- + " FROM scott.EMP\n"
- + " WHERE DEPTNO = 10\n"
+ + "FROM scott.EMP\n"
+ + "WHERE DEPTNO = 10\n"
+ "UNION ALL\n"
- + " SELECT *\n"
- + " FROM scott.EMP\n"
- + " WHERE DEPTNO = 20";
+ + "SELECT *\n"
+ + "FROM scott.EMP\n"
+ + "WHERE DEPTNO = 20";
pig(script)
.assertRel("B1", false,
hasTree("LogicalFilter(condition=[=($7, 10)])\n"
@@ -377,11 +377,11 @@ public class PigRelOpTest extends PigRelTestBase {
+ " $cor1.SAL, $cor1.COMM, $cor1.DEPTNO0 AS DEPTNO\n"
+ "FROM (SELECT DEPTNO, JOB, COLLECT(ROW(EMPNO, ENAME, JOB, MGR, "
+ "HIREDATE, SAL, COMM, DEPTNO)) AS $f2\n"
- + " FROM scott.EMP\n"
- + " WHERE JOB <> 'CLERK'\n"
- + " GROUP BY DEPTNO, JOB) AS $cor1,\n"
- + " LATERAL UNNEST (SELECT $cor1.$f2 AS $f0\n"
- + " FROM (VALUES (0)) AS t (ZERO)) AS t3 (EMPNO, ENAME, JOB,"
+ + " FROM scott.EMP\n"
+ + " WHERE JOB <> 'CLERK'\n"
+ + " GROUP BY DEPTNO, JOB) AS $cor1,\n"
+ + " LATERAL UNNEST (SELECT $cor1.$f2 AS $f0\n"
+ + " FROM (VALUES (0)) AS t (ZERO)) AS t3 (EMPNO, ENAME, JOB,"
+ " MGR, HIREDATE, SAL, COMM, DEPTNO) AS t30\n"
+ "ORDER BY $cor1.DEPTNO, $cor1.JOB";
pig(script).assertRel(hasTree(plan))
@@ -464,20 +464,20 @@ public class PigRelOpTest extends PigRelTestBase {
+ "FROM (SELECT $cor4.DEPTNO AS group, "
+ "COUNT(PIG_BAG($cor4.X)) AS cnt, $cor4.X, "
+ "BigDecimalMax(PIG_BAG(MULTISET_PROJECTION($cor4.X, 3))) AS $f3\n"
- + " FROM (SELECT DEPTNO, COLLECT(ROW(EMPNO, ENAME, JOB, MGR, "
+ + " FROM (SELECT DEPTNO, COLLECT(ROW(EMPNO, ENAME, JOB, MGR, "
+ "HIREDATE, SAL, COMM, DEPTNO)) AS A\n"
- + " FROM scott.EMP\n"
- + " GROUP BY DEPTNO) AS $cor4,\n"
- + " LATERAL (SELECT COLLECT(ROW(ENAME, JOB, DEPTNO, SAL)) AS X\n"
- + " FROM (SELECT ENAME, JOB, DEPTNO, SAL\n"
- + " FROM UNNEST (SELECT $cor4.A AS $f0\n"
- + " FROM (VALUES (0)) AS t (ZERO)) "
+ + " FROM scott.EMP\n"
+ + " GROUP BY DEPTNO) AS $cor4,\n"
+ + " LATERAL (SELECT COLLECT(ROW(ENAME, JOB, DEPTNO, SAL)) AS X\n"
+ + " FROM (SELECT ENAME, JOB, DEPTNO, SAL\n"
+ + " FROM UNNEST (SELECT $cor4.A AS $f0\n"
+ + " FROM (VALUES (0)) AS t (ZERO)) "
+ "AS t2 (EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM, DEPTNO)\n"
- + " WHERE JOB <> 'CLERK'\n"
- + " ORDER BY SAL) AS t5\n"
- + " GROUP BY 'all') AS t8) AS $cor5,\n"
- + " LATERAL UNNEST (SELECT $cor5.X AS $f0\n"
- + " FROM (VALUES (0)) AS t (ZERO)) "
+ + " WHERE JOB <> 'CLERK'\n"
+ + " ORDER BY SAL) AS t5\n"
+ + " GROUP BY 'all') AS t8) AS $cor5,\n"
+ + " LATERAL UNNEST (SELECT $cor5.X AS $f0\n"
+ + " FROM (VALUES (0)) AS t (ZERO)) "
+ "AS t11 (ENAME, JOB, DEPTNO, SAL) AS t110\n"
+ "ORDER BY $cor5.group";
pig(script).assertRel(hasTree(plan))
@@ -510,12 +510,12 @@ public class PigRelOpTest extends PigRelTestBase {
+ "(7902,FORD,ANALYST,7566,1981-12-03,3000.00,null,20)\n";
final String sql = ""
+ "SELECT *\n"
- + " FROM scott.EMP\n"
- + " WHERE DEPTNO = 10\n"
+ + "FROM scott.EMP\n"
+ + "WHERE DEPTNO = 10\n"
+ "UNION ALL\n"
- + " SELECT *\n"
- + " FROM scott.EMP\n"
- + " WHERE DEPTNO = 20";
+ + "SELECT *\n"
+ + "FROM scott.EMP\n"
+ + "WHERE DEPTNO = 20";
pig(script).assertRel(hasTree(plan))
.assertResult(is(result))
.assertSql(is(sql));
@@ -548,11 +548,11 @@ public class PigRelOpTest extends PigRelTestBase {
+ "(40,OPERATIONS,null)\n";
final String sql = ""
+ "SELECT *\n"
- + " FROM scott.DEPT\n"
+ + "FROM scott.DEPT\n"
+ "UNION ALL\n"
- + " SELECT DEPTNO, DNAME, "
+ + "SELECT DEPTNO, DNAME, "
+ "CAST(NULL AS VARCHAR CHARACTER SET ISO-8859-1) AS LOC\n"
- + " FROM scott.DEPT";
+ + "FROM scott.DEPT";
pig(script).assertRel(hasTree(plan))
.assertOptimizedRel(hasTree(optimizedPlan))
.assertResult(is(result))
@@ -592,16 +592,16 @@ public class PigRelOpTest extends PigRelTestBase {
+ "SELECT EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM, DEPTNO, "
+ "CAST(NULL AS VARCHAR CHARACTER SET ISO-8859-1) AS DNAME, "
+ "CAST(NULL AS VARCHAR CHARACTER SET ISO-8859-1) AS LOC\n"
- + " FROM scott.EMP\n"
- + " WHERE DEPTNO = 10\n"
+ + "FROM scott.EMP\n"
+ + "WHERE DEPTNO = 10\n"
+ "UNION ALL\n"
- + " SELECT CAST(NULL AS INTEGER) AS EMPNO, "
+ + "SELECT CAST(NULL AS INTEGER) AS EMPNO, "
+ "CAST(NULL AS VARCHAR CHARACTER SET ISO-8859-1) AS ENAME, "
+ "CAST(NULL AS VARCHAR CHARACTER SET ISO-8859-1) AS JOB, "
+ "CAST(NULL AS INTEGER) AS MGR, "
+ "CAST(NULL AS DATE) AS HIREDATE, CAST(NULL AS DECIMAL(19, 0)) AS SAL, "
+ "CAST(NULL AS DECIMAL(19, 0)) AS COMM, DEPTNO, DNAME, LOC\n"
- + " FROM scott.DEPT";
+ + "FROM scott.DEPT";
pig(script).assertRel(hasTree(plan))
.assertResult(is(result))
.assertSql(is(sql));
@@ -720,8 +720,8 @@ public class PigRelOpTest extends PigRelTestBase {
+ "FROM scott.EMP\n"
+ " INNER JOIN scott.DEPT ON EMP.DEPTNO = DEPT.DEPTNO\n"
+ " INNER JOIN (SELECT *\n"
- + " FROM scott.DEPT\n"
- + " WHERE LOC = 'CHICAGO') AS t ON EMP.DEPTNO = t.DEPTNO";
+ + " FROM scott.DEPT\n"
+ + " WHERE LOC = 'CHICAGO') AS t ON EMP.DEPTNO = t.DEPTNO";
final String result = ""
+ "(7499,ALLEN,SALESMAN,7698,1981-02-20,1600.00,300.00,30,30,"
+ "SALES,CHICAGO,30,SALES,"
@@ -769,8 +769,8 @@ public class PigRelOpTest extends PigRelTestBase {
+ " INNER JOIN scott.DEPT ON EMP.DEPTNO = DEPT.DEPTNO "
+ "AND EMP.ENAME = DEPT.DNAME\n"
+ " INNER JOIN (SELECT *\n"
- + " FROM scott.DEPT\n"
- + " WHERE LOC = 'CHICAGO') AS t ON EMP.DEPTNO = t.DEPTNO "
+ + " FROM scott.DEPT\n"
+ + " WHERE LOC = 'CHICAGO') AS t ON EMP.DEPTNO = t.DEPTNO "
+ "AND DEPT.DNAME = t.DNAME";
pig(script2).assertRel(hasTree(plan2))
.assertSql(is(sql2));
@@ -793,10 +793,10 @@ public class PigRelOpTest extends PigRelTestBase {
final String sql = ""
+ "SELECT *\n"
+ "FROM (SELECT DEPTNO\n"
- + " FROM scott.DEPT) AS t,\n"
- + " (SELECT DEPTNO\n"
- + " FROM scott.DEPT\n"
- + " WHERE DEPTNO <= 20) AS t1";
+ + " FROM scott.DEPT) AS t,\n"
+ + " (SELECT DEPTNO\n"
+ + " FROM scott.DEPT\n"
+ + " WHERE DEPTNO <= 20) AS t1";
final String result = ""
+ "(10,10)\n"
+ "(10,20)\n"
@@ -850,13 +850,13 @@ public class PigRelOpTest extends PigRelTestBase {
final String sql2 = ""
+ "SELECT *\n"
+ "FROM (SELECT DEPTNO\n"
- + " FROM scott.DEPT) AS t,\n"
- + " (SELECT DEPTNO\n"
- + " FROM scott.DEPT\n"
- + " WHERE DEPTNO <= 20) AS t1,\n"
- + " (SELECT DEPTNO\n"
- + " FROM scott.DEPT\n"
- + " WHERE DEPTNO > 20) AS t3";
+ + " FROM scott.DEPT) AS t,\n"
+ + " (SELECT DEPTNO\n"
+ + " FROM scott.DEPT\n"
+ + " WHERE DEPTNO <= 20) AS t1,\n"
+ + " (SELECT DEPTNO\n"
+ + " FROM scott.DEPT\n"
+ + " WHERE DEPTNO > 20) AS t3";
pig(script2).assertRel(hasTree(plan2))
.assertResult(is(result2))
.assertSql(is(sql2));
@@ -1591,19 +1591,21 @@ public class PigRelOpTest extends PigRelTestBase {
+ "AS DEPTNO, t4.A, t4.B, t7.C\n"
+ "FROM (SELECT CASE WHEN t0.$f0 IS NOT NULL THEN t0.$f0 ELSE t3.DEPTNO END "
+ "AS DEPTNO, t0.A, t3.B\n"
- + " FROM (SELECT DEPTNO + 10 AS $f0, COLLECT(ROW(DEPTNO, DNAME, LOC)) AS A\n"
- + " FROM scott.DEPT\n"
- + " GROUP BY DEPTNO + 10) AS t0\n"
- + " FULL JOIN (SELECT CAST(DEPTNO AS INTEGER) AS DEPTNO, COLLECT(ROW"
- + "(DEPTNO, DNAME, LOC)) AS B\n"
- + " FROM scott.DEPT\n"
- + " WHERE DEPTNO <= 30\n"
- + " GROUP BY CAST(DEPTNO AS INTEGER)) AS t3 ON t0.$f0 = t3.DEPTNO) AS t4\n"
+ + " FROM (SELECT DEPTNO + 10 AS $f0, "
+ + "COLLECT(ROW(DEPTNO, DNAME, LOC)) AS A\n"
+ + " FROM scott.DEPT\n"
+ + " GROUP BY DEPTNO + 10) AS t0\n"
+ + " FULL JOIN (SELECT CAST(DEPTNO AS INTEGER) AS DEPTNO, "
+ + "COLLECT(ROW(DEPTNO, DNAME, LOC)) AS B\n"
+ + " FROM scott.DEPT\n"
+ + " WHERE DEPTNO <= 30\n"
+ + " GROUP BY CAST(DEPTNO AS INTEGER)) AS t3 "
+ + "ON t0.$f0 = t3.DEPTNO) AS t4\n"
+ " FULL JOIN (SELECT CAST(DEPTNO AS INTEGER) AS DEPTNO, COLLECT(ROW(DEPTNO, DNAME, "
+ "LOC)) AS C\n"
- + " FROM scott.DEPT\n"
- + " WHERE DEPTNO >= 20\n"
- + " GROUP BY CAST(DEPTNO AS INTEGER)) AS t7 ON t4.DEPTNO = t7.DEPTNO\n"
+ + " FROM scott.DEPT\n"
+ + " WHERE DEPTNO >= 20\n"
+ + " GROUP BY CAST(DEPTNO AS INTEGER)) AS t7 ON t4.DEPTNO = t7.DEPTNO\n"
+ "ORDER BY CASE WHEN t4.DEPTNO IS NOT NULL THEN t4.DEPTNO ELSE t7.DEPTNO END";
pig(script).assertRel(hasTree(plan))
.assertResult(is(result))
diff --git a/server/src/main/java/org/apache/calcite/sql/ddl/SqlDdlNodes.java b/server/src/main/java/org/apache/calcite/sql/ddl/SqlDdlNodes.java
index 40fe4cc..d5ac495 100644
--- a/server/src/main/java/org/apache/calcite/sql/ddl/SqlDdlNodes.java
+++ b/server/src/main/java/org/apache/calcite/sql/ddl/SqlDdlNodes.java
@@ -30,7 +30,7 @@ import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.SqlNodeList;
import org.apache.calcite.sql.SqlOperator;
import org.apache.calcite.sql.SqlSelect;
-import org.apache.calcite.sql.dialect.CalciteSqlDialect;
+import org.apache.calcite.sql.SqlWriterConfig;
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.sql.parser.SqlParseException;
import org.apache.calcite.sql.parser.SqlParserPos;
@@ -46,8 +46,6 @@ import org.apache.calcite.util.Util;
import com.google.common.collect.ImmutableList;
-import java.io.PrintWriter;
-import java.io.StringWriter;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;
@@ -214,8 +212,7 @@ public class SqlDdlNodes {
return query;
}
final SqlParserPos p = query.getParserPosition();
- final SqlNodeList selectList =
- new SqlNodeList(ImmutableList.<SqlNode>of(SqlIdentifier.star(p)), p);
+ final SqlNodeList selectList = SqlNodeList.SINGLETON_STAR;
final SqlCall from =
SqlStdOperatorTable.AS.createCall(p,
ImmutableList.<SqlNode>builder()
@@ -238,16 +235,15 @@ public class SqlDdlNodes {
.build();
final Planner planner = Frameworks.getPlanner(config);
try {
- final StringWriter sw = new StringWriter();
- final PrintWriter pw = new PrintWriter(sw);
- final SqlPrettyWriter w =
- new SqlPrettyWriter(CalciteSqlDialect.DEFAULT, false, pw);
- pw.print("INSERT INTO ");
+ final StringBuilder buf = new StringBuilder();
+ final SqlWriterConfig writerConfig =
+ SqlPrettyWriter.config().withAlwaysUseParentheses(false);
+ final SqlPrettyWriter w = new SqlPrettyWriter(writerConfig, buf);
+ buf.append("INSERT INTO ");
name.unparse(w, 0, 0);
- pw.print(" ");
+ buf.append(' ');
query.unparse(w, 0, 0);
- pw.flush();
- final String sql = sw.toString();
+ final String sql = buf.toString();
final SqlNode query1 = planner.parse(sql);
final SqlNode query2 = planner.validate(query1);
final RelRoot r = planner.rel(query2);