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 2016/09/16 04:48:16 UTC
[2/6] calcite git commit: [CALCITE-1095] NOT precedence
[CALCITE-1095] NOT precedence
Previously prefix operators such as NOT were not handled in the same
way as infix and postfix operators. With a new precedence climbing
parser, we can handle all operators uniformly.
Document precedence of all operators, adjusting some to match the SQL
standard; for example, LIKE now has slightly higher precedence than =,
and NOT has lower precedence than =.
Add a test to ensure that operators' real rankings match the documentation.
Project: http://git-wip-us.apache.org/repos/asf/calcite/repo
Commit: http://git-wip-us.apache.org/repos/asf/calcite/commit/339ac13e
Tree: http://git-wip-us.apache.org/repos/asf/calcite/tree/339ac13e
Diff: http://git-wip-us.apache.org/repos/asf/calcite/diff/339ac13e
Branch: refs/heads/master
Commit: 339ac13eb23b5aac6cadc16c42e36deebbcd1e39
Parents: f9724c2
Author: Julian Hyde <jh...@apache.org>
Authored: Thu Feb 18 11:14:52 2016 -0800
Committer: Julian Hyde <jh...@apache.org>
Committed: Thu Sep 15 17:02:42 2016 -0700
----------------------------------------------------------------------
core/src/main/codegen/templates/Parser.jj | 69 +--
.../apache/calcite/sql/SqlPrefixOperator.java | 2 +-
.../apache/calcite/sql/SqlSpecialOperator.java | 40 +-
.../calcite/sql/fun/SqlBetweenOperator.java | 42 +-
.../apache/calcite/sql/fun/SqlInOperator.java | 2 +-
.../apache/calcite/sql/fun/SqlItemOperator.java | 19 +-
.../apache/calcite/sql/fun/SqlLikeOperator.java | 22 +-
.../calcite/sql/fun/SqlStdOperatorTable.java | 26 +-
.../calcite/sql/parser/SqlParserUtil.java | 387 ++++++++--------
.../calcite/util/PrecedenceClimbingParser.java | 447 +++++++++++++++++++
.../main/java/org/apache/calcite/util/Util.java | 2 +-
.../calcite/sql/parser/SqlParserTest.java | 104 +++--
.../apache/calcite/sql/test/SqlTesterImpl.java | 26 +-
.../org/apache/calcite/test/CalciteSuite.java | 2 +
.../apache/calcite/test/SqlValidatorTest.java | 181 ++++++++
.../util/PrecedenceClimbingParserTest.java | 181 ++++++++
core/src/test/resources/sql/operator.iq | 69 +++
site/_docs/reference.md | 18 +
18 files changed, 1310 insertions(+), 329 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/calcite/blob/339ac13e/core/src/main/codegen/templates/Parser.jj
----------------------------------------------------------------------
diff --git a/core/src/main/codegen/templates/Parser.jj b/core/src/main/codegen/templates/Parser.jj
index 50f762c..43d6cd5 100644
--- a/core/src/main/codegen/templates/Parser.jj
+++ b/core/src/main/codegen/templates/Parser.jj
@@ -2556,13 +2556,30 @@ SqlNode Expression(ExprContext exprContext) :
// TODO jvs 15-Nov-2003: ANY/ALL
+void Expression2b(ExprContext exprContext, List<Object> list) :
+{
+ SqlNode e;
+ SqlOperator op;
+}
+{
+ (
+ op = PrefixRowOperator() {
+ checkNonQueryExpression(exprContext);
+ list.add(new SqlParserUtil.ToTreeListItem(op, getPos()));
+ }
+ )*
+ e = Expression3(exprContext) {
+ list.add(e);
+ }
+}
+
/**
* Parses a binary row expression, or a parenthesized expression of any
* kind.
*
* <p>The result is as a flat list of operators and operands. The top-level
* call to get an expression should call {@link #Expression}, but lower-level
- * calls should call this, to give the parser the opos to associate
+ * calls should call this, to give the parser the opportunity to associate
* operator calls.
*
* <p>For example 'a = b like c = d' should come out '((a = b) like c) = d'
@@ -2572,17 +2589,15 @@ SqlNode Expression(ExprContext exprContext) :
*/
List<Object> Expression2(ExprContext exprContext) :
{
- List<Object> list, list2;
+ final List<Object> list = new ArrayList();
+ List<Object> list2;
SqlNodeList nodeList;
SqlNode e;
SqlOperator op;
- SqlParserPos pos;
+ SqlParserPos pos = getPos();
}
{
- e = Expression3(exprContext)
- {
- list = startList(e);
- }
+ Expression2b(exprContext, list)
(
(
LOOKAHEAD(2)
@@ -2599,7 +2614,7 @@ List<Object> Expression2(ExprContext exprContext) :
op = SqlStdOperatorTable.NOT_IN;
pos = getPos();
}
- |
+ |
<IN>
{
op = SqlStdOperatorTable.IN;
@@ -2622,7 +2637,7 @@ List<Object> Expression2(ExprContext exprContext) :
list.add(nodeList);
}
}
- |
+ |
LOOKAHEAD(2)
{
checkNonQueryExpression(exprContext);
@@ -2635,10 +2650,10 @@ List<Object> Expression2(ExprContext exprContext) :
}
[
<SYMMETRIC> { op = SqlStdOperatorTable.SYMMETRIC_NOT_BETWEEN; }
- |
+ |
<ASYMMETRIC>
]
- |
+ |
<BETWEEN>
{
op = SqlStdOperatorTable.BETWEEN;
@@ -2646,7 +2661,7 @@ List<Object> Expression2(ExprContext exprContext) :
}
[
<SYMMETRIC> { op = SqlStdOperatorTable.SYMMETRIC_BETWEEN; }
- |
+ |
<ASYMMETRIC>
]
)
@@ -2655,7 +2670,7 @@ List<Object> Expression2(ExprContext exprContext) :
list.add(new SqlParserUtil.ToTreeListItem(op, pos));
list.add(e);
}
- |
+ |
{
checkNonQueryExpression(exprContext);
pos = getPos();
@@ -2664,12 +2679,12 @@ List<Object> Expression2(ExprContext exprContext) :
<NOT>
(
<LIKE> { op = SqlStdOperatorTable.NOT_LIKE; }
- |
+ |
<SIMILAR> <TO> { op = SqlStdOperatorTable.NOT_SIMILAR_TO; }
)
- |
+ |
<LIKE> { op = SqlStdOperatorTable.LIKE; }
- |
+ |
<SIMILAR> <TO> { op = SqlStdOperatorTable.SIMILAR_TO; }
)
list2 = Expression2(ExprContext.ACCEPT_SUBQUERY)
@@ -2688,17 +2703,14 @@ List<Object> Expression2(ExprContext exprContext) :
list.add(e);
}
]
- |
+ |
LOOKAHEAD(3) op = BinaryRowOperator()
{
checkNonQueryExpression(exprContext);
- }
- e = Expression3(ExprContext.ACCEPT_SUBQUERY)
- {
list.add(new SqlParserUtil.ToTreeListItem(op, getPos()));
- list.add(e);
}
- |
+ Expression2b(ExprContext.ACCEPT_SUBQUERY, list)
+ |
<LBRACKET>
e = Expression(ExprContext.ACCEPT_SUBQUERY)
<RBRACKET>
@@ -2708,7 +2720,7 @@ List<Object> Expression2(ExprContext exprContext) :
SqlStdOperatorTable.ITEM, getPos()));
list.add(e);
}
- |
+ |
{
checkNonQueryExpression(exprContext);
}
@@ -2766,17 +2778,6 @@ SqlNode Expression3(ExprContext exprContext) :
return SqlStdOperatorTable.ROW.createCall(list);
}
|
- op = PrefixRowOperator()
- {
- prefixRowOpPos = getPos();
- checkNonQueryExpression(exprContext);
- }
- e = Expression3(ExprContext.ACCEPT_SUBQUERY)
- {
- SqlParserPos callPos = prefixRowOpPos.plus(e.getParserPosition());
- return op.createCall(callPos, e);
- }
- |
{
pos = getPos();
}
http://git-wip-us.apache.org/repos/asf/calcite/blob/339ac13e/core/src/main/java/org/apache/calcite/sql/SqlPrefixOperator.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlPrefixOperator.java b/core/src/main/java/org/apache/calcite/sql/SqlPrefixOperator.java
index 11eeb26..af44dd2 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlPrefixOperator.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlPrefixOperator.java
@@ -42,7 +42,7 @@ public class SqlPrefixOperator extends SqlOperator {
super(
name,
kind,
- leftPrec(0, true),
+ leftPrec(prec, true),
rightPrec(prec, true),
returnTypeInference,
operandTypeInference,
http://git-wip-us.apache.org/repos/asf/calcite/blob/339ac13e/core/src/main/java/org/apache/calcite/sql/SqlSpecialOperator.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlSpecialOperator.java b/core/src/main/java/org/apache/calcite/sql/SqlSpecialOperator.java
index 85da998..83049ac 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlSpecialOperator.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlSpecialOperator.java
@@ -16,12 +16,14 @@
*/
package org.apache.calcite.sql;
+import org.apache.calcite.sql.parser.SqlParserPos;
import org.apache.calcite.sql.type.SqlOperandTypeChecker;
import org.apache.calcite.sql.type.SqlOperandTypeInference;
import org.apache.calcite.sql.type.SqlReturnTypeInference;
+import org.apache.calcite.util.PrecedenceClimbingParser;
import org.apache.calcite.util.Util;
-import java.util.List;
+import com.google.common.base.Predicate;
/**
* Generic operator for nodes with special syntax.
@@ -81,11 +83,43 @@ public class SqlSpecialOperator extends SqlOperator {
* {@link SqlNode}
* @return ordinal of the node which replaced the expression
*/
- public int reduceExpr(
+ public ReduceResult reduceExpr(
int ordinal,
- List<Object> list) {
+ TokenSequence list) {
throw Util.needToImplement(this);
}
+
+ /** List of tokens: the input to a parser. Every token is either an operator
+ * ({@link SqlOperator}) or an expression ({@link SqlNode}), and every token
+ * has a position. */
+ public interface TokenSequence {
+ int size();
+ SqlOperator op(int i);
+ SqlParserPos pos(int i);
+ boolean isOp(int i);
+ SqlNode node(int i);
+ void replaceSublist(int start, int end, SqlNode e);
+
+ /** Creates a parser whose token sequence is a copy of a subset of this
+ * token sequence. */
+ PrecedenceClimbingParser parser(int start,
+ Predicate<PrecedenceClimbingParser.Token> predicate);
+ }
+
+ /** Result of applying
+ * {@link org.apache.calcite.util.PrecedenceClimbingParser.Special#apply}.
+ * Tells the caller which range of tokens to replace, and with what. */
+ public class ReduceResult {
+ public final int startOrdinal;
+ public final int endOrdinal;
+ public final SqlNode node;
+
+ public ReduceResult(int startOrdinal, int endOrdinal, SqlNode node) {
+ this.startOrdinal = startOrdinal;
+ this.endOrdinal = endOrdinal;
+ this.node = node;
+ }
+ }
}
// End SqlSpecialOperator.java
http://git-wip-us.apache.org/repos/asf/calcite/blob/339ac13e/core/src/main/java/org/apache/calcite/sql/fun/SqlBetweenOperator.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlBetweenOperator.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlBetweenOperator.java
index 86660a2..bf23c3f 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlBetweenOperator.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlBetweenOperator.java
@@ -109,7 +109,7 @@ public class SqlBetweenOperator extends SqlInfixOperator {
//~ Constructors -----------------------------------------------------------
public SqlBetweenOperator(Flag flag, boolean negated) {
- super(negated ? NOT_BETWEEN_NAMES : BETWEEN_NAMES, SqlKind.BETWEEN, 30,
+ super(negated ? NOT_BETWEEN_NAMES : BETWEEN_NAMES, SqlKind.BETWEEN, 32,
null, InferTypes.FIRST_KNOWN, OTC_CUSTOM);
this.flag = flag;
this.negated = negated;
@@ -191,12 +191,8 @@ public class SqlBetweenOperator extends SqlInfixOperator {
writer.endList(frame);
}
- public int reduceExpr(
- int opOrdinal,
- List<Object> list) {
- final SqlParserUtil.ToTreeListItem betweenNode =
- (SqlParserUtil.ToTreeListItem) list.get(opOrdinal);
- SqlOperator op = betweenNode.getOperator();
+ public ReduceResult reduceExpr(int opOrdinal, TokenSequence list) {
+ SqlOperator op = list.op(opOrdinal);
assert op == this;
// Break the expression up into expressions. For example, a simple
@@ -208,26 +204,18 @@ public class SqlBetweenOperator extends SqlInfixOperator {
// |_____| |_____| |_____|
// exp0 exp1 exp2
// Create the expression between 'BETWEEN' and 'AND'.
- final SqlParserPos pos =
- ((SqlNode) list.get(opOrdinal + 1)).getParserPosition();
SqlNode exp1 =
SqlParserUtil.toTreeEx(list, opOrdinal + 1, 0, SqlKind.AND);
if ((opOrdinal + 2) >= list.size()) {
- SqlParserPos lastPos =
- ((SqlNode) list.get(list.size() - 1)).getParserPosition();
+ SqlParserPos lastPos = list.pos(list.size() - 1);
final int line = lastPos.getEndLineNum();
final int col = lastPos.getEndColumnNum() + 1;
SqlParserPos errPos = new SqlParserPos(line, col, line, col);
throw SqlUtil.newContextException(errPos, RESOURCE.betweenWithoutAnd());
}
- final Object o = list.get(opOrdinal + 2);
- if (!(o instanceof SqlParserUtil.ToTreeListItem)) {
- SqlParserPos errPos = ((SqlNode) o).getParserPosition();
- throw SqlUtil.newContextException(errPos, RESOURCE.betweenWithoutAnd());
- }
- if (((SqlParserUtil.ToTreeListItem) o).getOperator().getKind()
- != SqlKind.AND) {
- SqlParserPos errPos = ((SqlParserUtil.ToTreeListItem) o).getPos();
+ if (!list.isOp(opOrdinal + 2)
+ || list.op(opOrdinal + 2).getKind() != SqlKind.AND) {
+ SqlParserPos errPos = list.pos(opOrdinal + 2);
throw SqlUtil.newContextException(errPos, RESOURCE.betweenWithoutAnd());
}
@@ -240,30 +228,22 @@ public class SqlBetweenOperator extends SqlInfixOperator {
// (a BETWEEN b AND c + d) OR e
// because OR has lower precedence than BETWEEN.
SqlNode exp2 =
- SqlParserUtil.toTreeEx(
- list,
+ SqlParserUtil.toTreeEx(list,
opOrdinal + 3,
getRightPrec(),
SqlKind.OTHER);
// Create the call.
- SqlNode exp0 = (SqlNode) list.get(opOrdinal - 1);
+ SqlNode exp0 = list.node(opOrdinal - 1);
SqlCall newExp =
createCall(
- betweenNode.getPos(),
+ list.pos(opOrdinal),
exp0,
exp1,
exp2);
// Replace all of the matched nodes with the single reduced node.
- SqlParserUtil.replaceSublist(
- list,
- opOrdinal - 1,
- opOrdinal + 4,
- newExp);
-
- // Return the ordinal of the new current node.
- return opOrdinal - 1;
+ return new ReduceResult(opOrdinal - 1, opOrdinal + 4, newExp);
}
//~ Inner Classes ----------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/calcite/blob/339ac13e/core/src/main/java/org/apache/calcite/sql/fun/SqlInOperator.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlInOperator.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlInOperator.java
index a20cea3..716902b 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlInOperator.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlInOperator.java
@@ -66,7 +66,7 @@ public class SqlInOperator extends SqlBinaryOperator {
super(
isNotIn ? "NOT IN" : "IN",
SqlKind.IN,
- 30,
+ 32,
true,
ReturnTypes.BOOLEAN_NULLABLE,
InferTypes.FIRST_KNOWN,
http://git-wip-us.apache.org/repos/asf/calcite/blob/339ac13e/core/src/main/java/org/apache/calcite/sql/fun/SqlItemOperator.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlItemOperator.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlItemOperator.java
index 6b07a35..7cd0e1e 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlItemOperator.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlItemOperator.java
@@ -26,15 +26,12 @@ import org.apache.calcite.sql.SqlOperandCountRange;
import org.apache.calcite.sql.SqlOperatorBinding;
import org.apache.calcite.sql.SqlSpecialOperator;
import org.apache.calcite.sql.SqlWriter;
-import org.apache.calcite.sql.parser.SqlParserUtil;
import org.apache.calcite.sql.type.OperandTypes;
import org.apache.calcite.sql.type.SqlOperandCountRanges;
import org.apache.calcite.sql.type.SqlSingleOperandTypeChecker;
import org.apache.calcite.sql.type.SqlTypeFamily;
import org.apache.calcite.sql.type.SqlTypeName;
-import java.util.List;
-
/**
* The item operator {@code [ ... ]}, used to access a given element of an
* array or map. For example, {@code myArray[3]} or {@code "myMap['foo']"}.
@@ -51,22 +48,18 @@ class SqlItemOperator extends SqlSpecialOperator {
super("ITEM", SqlKind.OTHER_FUNCTION, 100, true, null, null, null);
}
- @Override public int reduceExpr(int ordinal, List<Object> list) {
- SqlNode left = (SqlNode) list.get(ordinal - 1);
- SqlNode right = (SqlNode) list.get(ordinal + 1);
- final SqlParserUtil.ToTreeListItem treeListItem =
- (SqlParserUtil.ToTreeListItem) list.get(ordinal);
- SqlParserUtil.replaceSublist(
- list,
- ordinal - 1,
+ @Override public ReduceResult reduceExpr(int ordinal,
+ TokenSequence list) {
+ SqlNode left = list.node(ordinal - 1);
+ SqlNode right = list.node(ordinal + 1);
+ return new ReduceResult(ordinal - 1,
ordinal + 2,
createCall(
left.getParserPosition()
.plus(right.getParserPosition())
- .plus(treeListItem.getPos()),
+ .plus(list.pos(ordinal)),
left,
right));
- return ordinal - 1;
}
@Override public void unparse(
http://git-wip-us.apache.org/repos/asf/calcite/blob/339ac13e/core/src/main/java/org/apache/calcite/sql/fun/SqlLikeOperator.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlLikeOperator.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlLikeOperator.java
index 1de1407..9a0187c 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlLikeOperator.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlLikeOperator.java
@@ -33,8 +33,6 @@ import org.apache.calcite.sql.type.SqlOperandCountRanges;
import org.apache.calcite.sql.type.SqlTypeUtil;
import org.apache.calcite.util.Util;
-import java.util.List;
-
/**
* An operator describing the <code>LIKE</code> and <code>SIMILAR</code>
* operators.
@@ -76,7 +74,7 @@ public class SqlLikeOperator extends SqlSpecialOperator {
super(
name,
kind,
- 30,
+ 32,
false,
ReturnTypes.BOOLEAN_NULLABLE,
InferTypes.FIRST_KNOWN,
@@ -148,16 +146,15 @@ public class SqlLikeOperator extends SqlSpecialOperator {
writer.endList(frame);
}
- public int reduceExpr(
+ public ReduceResult reduceExpr(
final int opOrdinal,
- List<Object> list) {
+ TokenSequence list) {
// Example:
// a LIKE b || c ESCAPE d || e AND f
// | | | | | |
// exp0 exp1 exp2
- SqlNode exp0 = (SqlNode) list.get(opOrdinal - 1);
- SqlOperator op =
- ((SqlParserUtil.ToTreeListItem) list.get(opOrdinal)).getOperator();
+ SqlNode exp0 = list.node(opOrdinal - 1);
+ SqlOperator op = list.op(opOrdinal);
assert op instanceof SqlLikeOperator;
SqlNode exp1 =
SqlParserUtil.toTreeEx(
@@ -167,10 +164,8 @@ public class SqlLikeOperator extends SqlSpecialOperator {
SqlKind.ESCAPE);
SqlNode exp2 = null;
if ((opOrdinal + 2) < list.size()) {
- final Object o = list.get(opOrdinal + 2);
- if (o instanceof SqlParserUtil.ToTreeListItem) {
- final SqlOperator op2 =
- ((SqlParserUtil.ToTreeListItem) o).getOperator();
+ if (list.isOp(opOrdinal + 2)) {
+ final SqlOperator op2 = list.op(opOrdinal + 2);
if (op2.getKind() == SqlKind.ESCAPE) {
exp2 =
SqlParserUtil.toTreeEx(
@@ -191,8 +186,7 @@ public class SqlLikeOperator extends SqlSpecialOperator {
end = opOrdinal + 2;
}
SqlCall call = createCall(SqlParserPos.ZERO, operands);
- SqlParserUtil.replaceSublist(list, opOrdinal - 1, end, call);
- return opOrdinal - 1;
+ return new ReduceResult(opOrdinal - 1, end, call);
}
}
http://git-wip-us.apache.org/repos/asf/calcite/blob/339ac13e/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java
----------------------------------------------------------------------
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 e94748f..d0e60d2 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
@@ -142,7 +142,7 @@ public class SqlStdOperatorTable extends ReflectiveSqlOperatorTable {
new SqlBinaryOperator(
"AND",
SqlKind.AND,
- 28,
+ 24,
true,
ReturnTypes.BOOLEAN_NULLABLE_OPTIMIZED,
InferTypes.BOOLEAN,
@@ -431,7 +431,7 @@ public class SqlStdOperatorTable extends ReflectiveSqlOperatorTable {
new SqlBinaryOperator(
"OR",
SqlKind.OR,
- 26,
+ 22,
true,
ReturnTypes.BOOLEAN_NULLABLE_OPTIMIZED,
InferTypes.BOOLEAN,
@@ -553,7 +553,7 @@ public class SqlStdOperatorTable extends ReflectiveSqlOperatorTable {
new SqlPostfixOperator(
"IS NOT NULL",
SqlKind.IS_NOT_NULL,
- 30,
+ 28,
ReturnTypes.BOOLEAN_NOT_NULL,
InferTypes.VARCHAR_1024,
OperandTypes.ANY);
@@ -562,7 +562,7 @@ public class SqlStdOperatorTable extends ReflectiveSqlOperatorTable {
new SqlPostfixOperator(
"IS NULL",
SqlKind.IS_NULL,
- 30,
+ 28,
ReturnTypes.BOOLEAN_NOT_NULL,
InferTypes.VARCHAR_1024,
OperandTypes.ANY);
@@ -571,7 +571,7 @@ public class SqlStdOperatorTable extends ReflectiveSqlOperatorTable {
new SqlPostfixOperator(
"IS NOT TRUE",
SqlKind.IS_NOT_TRUE,
- 30,
+ 28,
ReturnTypes.BOOLEAN_NOT_NULL,
InferTypes.BOOLEAN,
OperandTypes.BOOLEAN);
@@ -580,7 +580,7 @@ public class SqlStdOperatorTable extends ReflectiveSqlOperatorTable {
new SqlPostfixOperator(
"IS TRUE",
SqlKind.IS_TRUE,
- 30,
+ 28,
ReturnTypes.BOOLEAN_NOT_NULL,
InferTypes.BOOLEAN,
OperandTypes.BOOLEAN);
@@ -589,7 +589,7 @@ public class SqlStdOperatorTable extends ReflectiveSqlOperatorTable {
new SqlPostfixOperator(
"IS NOT FALSE",
SqlKind.IS_NOT_FALSE,
- 30,
+ 28,
ReturnTypes.BOOLEAN_NOT_NULL,
InferTypes.BOOLEAN,
OperandTypes.BOOLEAN);
@@ -598,7 +598,7 @@ public class SqlStdOperatorTable extends ReflectiveSqlOperatorTable {
new SqlPostfixOperator(
"IS FALSE",
SqlKind.IS_FALSE,
- 30,
+ 28,
ReturnTypes.BOOLEAN_NOT_NULL,
InferTypes.BOOLEAN,
OperandTypes.BOOLEAN);
@@ -607,7 +607,7 @@ public class SqlStdOperatorTable extends ReflectiveSqlOperatorTable {
new SqlPostfixOperator(
"IS NOT UNKNOWN",
SqlKind.IS_NOT_NULL,
- 30,
+ 28,
ReturnTypes.BOOLEAN_NOT_NULL,
InferTypes.BOOLEAN,
OperandTypes.BOOLEAN);
@@ -616,7 +616,7 @@ public class SqlStdOperatorTable extends ReflectiveSqlOperatorTable {
new SqlPostfixOperator(
"IS UNKNOWN",
SqlKind.IS_NULL,
- 30,
+ 28,
ReturnTypes.BOOLEAN_NOT_NULL,
InferTypes.BOOLEAN,
OperandTypes.BOOLEAN);
@@ -625,7 +625,7 @@ public class SqlStdOperatorTable extends ReflectiveSqlOperatorTable {
new SqlPostfixOperator(
"IS A SET",
SqlKind.OTHER,
- 30,
+ 28,
ReturnTypes.BOOLEAN,
null,
OperandTypes.MULTISET);
@@ -657,7 +657,7 @@ public class SqlStdOperatorTable extends ReflectiveSqlOperatorTable {
new SqlPrefixOperator(
"NOT",
SqlKind.NOT,
- 30,
+ 26,
ReturnTypes.ARG0,
InferTypes.BOOLEAN,
OperandTypes.BOOLEAN);
@@ -1054,7 +1054,7 @@ public class SqlStdOperatorTable extends ReflectiveSqlOperatorTable {
* SIMILAR TO expression.
*/
public static final SqlSpecialOperator ESCAPE =
- new SqlSpecialOperator("Escape", SqlKind.ESCAPE, 30);
+ new SqlSpecialOperator("ESCAPE", SqlKind.ESCAPE, 0);
public static final SqlCaseOperator CASE = SqlCaseOperator.INSTANCE;
http://git-wip-us.apache.org/repos/asf/calcite/blob/339ac13e/core/src/main/java/org/apache/calcite/sql/parser/SqlParserUtil.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/parser/SqlParserUtil.java b/core/src/main/java/org/apache/calcite/sql/parser/SqlParserUtil.java
index 03b0ac3..3c605a5 100644
--- a/core/src/main/java/org/apache/calcite/sql/parser/SqlParserUtil.java
+++ b/core/src/main/java/org/apache/calcite/sql/parser/SqlParserUtil.java
@@ -20,25 +20,32 @@ import org.apache.calcite.avatica.util.Casing;
import org.apache.calcite.rel.type.RelDataTypeSystem;
import org.apache.calcite.runtime.CalciteContextException;
import org.apache.calcite.sql.SqlBinaryOperator;
-import org.apache.calcite.sql.SqlCall;
import org.apache.calcite.sql.SqlIntervalLiteral;
import org.apache.calcite.sql.SqlIntervalQualifier;
import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.SqlLiteral;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.SqlNodeList;
+import org.apache.calcite.sql.SqlNumericLiteral;
import org.apache.calcite.sql.SqlOperator;
import org.apache.calcite.sql.SqlPostfixOperator;
+import org.apache.calcite.sql.SqlPrefixOperator;
import org.apache.calcite.sql.SqlSpecialOperator;
+import org.apache.calcite.sql.fun.SqlStdOperatorTable;
+import org.apache.calcite.util.PrecedenceClimbingParser;
import org.apache.calcite.util.SaffronProperties;
import org.apache.calcite.util.Util;
import org.apache.calcite.util.trace.CalciteTrace;
+import com.google.common.base.Predicate;
+
import org.slf4j.Logger;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
+import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.StringTokenizer;
@@ -99,6 +106,7 @@ public final class SqlParserUtil {
/**
* @deprecated this method is not localized for Farrago standards
*/
+ @Deprecated // to be removed before 2.0
public static java.sql.Date parseDate(String s) {
return java.sql.Date.valueOf(s);
}
@@ -106,6 +114,7 @@ public final class SqlParserUtil {
/**
* @deprecated Does not parse SQL:99 milliseconds
*/
+ @Deprecated // to be removed before 2.0
public static java.sql.Time parseTime(String s) {
return java.sql.Time.valueOf(s);
}
@@ -113,6 +122,7 @@ public final class SqlParserUtil {
/**
* @deprecated this method is not localized for Farrago standards
*/
+ @Deprecated // to be removed before 2.0
public static java.sql.Timestamp parseTimestamp(String s) {
return java.sql.Timestamp.valueOf(s);
}
@@ -227,6 +237,7 @@ public final class SqlParserUtil {
* Parses a Binary string. SQL:99 defines a binary string as a hexstring
* with EVEN nbr of hex digits.
*/
+ @Deprecated // to be removed before 2.0
public static byte[] parseBinaryString(String s) {
s = s.replaceAll(" ", "");
s = s.replaceAll("\n", "");
@@ -491,6 +502,7 @@ public final class SqlParserUtil {
return new ParsedCollation(charset, locale, strength);
}
+ @Deprecated // to be removed before 2.0
public static String[] toStringArray(List<String> list) {
return list.toArray(new String[list.size()]);
}
@@ -503,6 +515,7 @@ public final class SqlParserUtil {
return list.toArray();
}
+ @Deprecated // to be removed before 2.0
public static String rightTrim(
String s,
char c) {
@@ -541,8 +554,14 @@ public final class SqlParserUtil {
* taking operator precedence and associativity into account.
*/
public static SqlNode toTree(List<Object> list) {
+ if (list.size() == 1
+ && list.get(0) instanceof SqlNode) {
+ // Short-cut for the simple common case
+ return (SqlNode) list.get(0);
+ }
LOGGER.trace("Attempting to reduce {}", list);
- final SqlNode node = toTreeEx(list, 0, 0, SqlKind.OTHER);
+ final OldTokenSequenceImpl tokenSequence = new OldTokenSequenceImpl(list);
+ final SqlNode node = toTreeEx(tokenSequence, 0, 0, SqlKind.OTHER);
LOGGER.debug("Reduced {}", node);
return node;
}
@@ -563,182 +582,58 @@ public final class SqlParserUtil {
* we encounter a token of this kind.
* @return the root node of the tree which the list condenses into
*/
- public static SqlNode toTreeEx(
- List<Object> list,
- int start,
- int minPrec,
- SqlKind stopperKind) {
-// Make several passes over the list, and each pass, coalesce the
-// expressions with the highest precedence.
- outer:
- while (true) {
- final int count = list.size();
- if (count <= (start + 1)) {
- break;
- }
- int i = start + 1;
- while (i < count) {
- SqlOperator previous;
- SqlOperator current = ((ToTreeListItem) list.get(i)).op;
- SqlParserPos currentPos = ((ToTreeListItem) list.get(i)).pos;
- if ((stopperKind != SqlKind.OTHER)
- && (current.getKind() == stopperKind)) {
- break outer;
- }
- SqlOperator next;
- int previousRight;
- int left = current.getLeftPrec();
- int right = current.getRightPrec();
- if (left < minPrec) {
- break outer;
- }
- int nextLeft;
- if (current instanceof SqlBinaryOperator) {
- if (i == (start + 1)) {
- previous = null;
- previousRight = 0;
- } else {
- previous = ((ToTreeListItem) list.get(i - 2)).op;
- previousRight = previous.getRightPrec();
- }
- if (i == (count - 2)) {
- next = null;
- nextLeft = 0;
- } else {
- next = ((ToTreeListItem) list.get(i + 2)).op;
- nextLeft = next.getLeftPrec();
- if ((next.getKind() == stopperKind)
- && (stopperKind != SqlKind.OTHER)) {
- // Suppose we're looking at 'AND' in
- // a BETWEEN b OR c AND d
- //
- // Because 'AND' is our stopper token, we still
- // want to reduce 'b OR c', even though 'AND' has
- // higher precedence than 'OR'.
- nextLeft = 0;
+ public static SqlNode toTreeEx(SqlSpecialOperator.TokenSequence list,
+ int start, final int minPrec, final SqlKind stopperKind) {
+ final Predicate<PrecedenceClimbingParser.Token> predicate =
+ new Predicate<PrecedenceClimbingParser.Token>() {
+ public boolean apply(PrecedenceClimbingParser.Token t) {
+ if (t instanceof PrecedenceClimbingParser.Op) {
+ final SqlOperator op = ((ToTreeListItem) t.o).op;
+ return stopperKind != SqlKind.OTHER
+ && op.kind == stopperKind
+ || minPrec > 0
+ && op.getLeftPrec() < minPrec;
+ } else {
+ return false;
}
}
- if ((previousRight < left) && (right >= nextLeft)) {
- // For example,
- // i: 0 1 2 3 4 5 6 7 8
- // list: a + b * c * d + e
- // prec: 0 1 2 3 4 3 4 1 2 0
- //
- // At i == 3, we have the first '*' operator, and its
- // surrounding precedences obey the relation 2 < 3 and
- // 4 >= 3, so we can reduce (b * c) to a single node.
- SqlNode leftExp = (SqlNode) list.get(i - 1);
-
- // For example,
- // i: 0 1 2 3 4 5 6 7 8
- // list: a + b * c * d + e
- // prec: 0 1 2 3 4 3 4 1 2 0
- //
- // At i == 3, we have the first '*' operator, and its
- // surrounding precedences obey the relation 2 < 3 and
- // 4 >= 3, so we can reduce (b * c) to a single node.
- SqlNode rightExp = (SqlNode) list.get(i + 1);
- SqlParserPos callPos =
- currentPos.plusAll(
- new SqlNode[]{leftExp, rightExp});
- final SqlCall newExp =
- current.createCall(callPos, leftExp, rightExp);
- LOGGER.debug("Reduced infix: {}", newExp);
-
- // Replace elements {i - 1, i, i + 1} with the new
- // expression.
- replaceSublist(list, i - 1, i + 2, newExp);
- break;
- }
- i += 2;
- } else if (current instanceof SqlPostfixOperator) {
- if (i == (start + 1)) {
- previous = null;
- previousRight = 0;
- } else {
- previous = ((ToTreeListItem) list.get(i - 2)).op;
- previousRight = previous.getRightPrec();
- }
- if (previousRight < left) {
- // For example,
- // i: 0 1 2 3 4 5 6 7 8
- // list: a + b * c ! + d
- // prec: 0 1 2 3 4 3 0 2
- //
- // At i == 3, we have the postfix '!' operator. Its
- // high precedence determines that it binds with 'b *
- // c'. The precedence of the following '+' operator is
- // irrelevant.
- SqlNode leftExp = (SqlNode) list.get(i - 1);
-
- SqlParserPos callPos =
- currentPos.plusAll(new SqlNode[]{leftExp});
- final SqlCall newExp =
- current.createCall(callPos, leftExp);
- LOGGER.debug("Reduced postfix: {}", newExp);
-
- // Replace elements {i - 1, i} with the new expression.
- list.remove(i);
- list.set(i - 1, newExp);
- break;
- }
- ++i;
-
- //
- } else if (current instanceof SqlSpecialOperator) {
- SqlSpecialOperator specOp = (SqlSpecialOperator) current;
-
- // We decide to reduce a special operator only on the basis
- // of what's to the left of it. The operator then decides
- // how far to the right to chew off.
- if (i == (start + 1)) {
- previous = null;
- previousRight = 0;
- } else {
- previous = ((ToTreeListItem) list.get(i - 2)).op;
- previousRight = previous.getRightPrec();
- }
- int nextOrdinal = i + 2;
- if (i == (count - 2)) {
- next = null;
- nextLeft = 0;
- } else {
- // find next op
- next = null;
- nextLeft = 0;
- for (; nextOrdinal < count; nextOrdinal++) {
- Object listItem = list.get(nextOrdinal);
- if (listItem instanceof ToTreeListItem) {
- next = ((ToTreeListItem) listItem).op;
- nextLeft = next.getLeftPrec();
- if ((stopperKind != SqlKind.OTHER)
- && (next.getKind() == stopperKind)) {
- break outer;
- } else {
- break;
- }
- }
- }
- }
- if (nextLeft < minPrec) {
- break outer;
- }
- if ((previousRight < left) && (right >= nextLeft)) {
- i = specOp.reduceExpr(i, list);
- LOGGER.debug("Reduced special op: {}", list.get(i));
- break;
- }
- i = nextOrdinal;
- } else {
- throw Util.newInternal("Unexpected operator type: " + current);
- }
- }
+ };
+ PrecedenceClimbingParser parser = list.parser(start, predicate);
+ final int beforeSize = parser.all().size();
+ parser.partialParse();
+ final int afterSize = parser.all().size();
+ final SqlNode node = convert(parser.all().get(0));
+ list.replaceSublist(start, start + beforeSize - afterSize + 1, node);
+ return node;
+ }
- // Require the list shrinks each time around -- otherwise we will
- // never terminate.
- assert list.size() < count;
+ private static SqlNode convert(PrecedenceClimbingParser.Token token) {
+ switch (token.type) {
+ case ATOM:
+ return (SqlNode) token.o;
+ case CALL:
+ final PrecedenceClimbingParser.Call call =
+ (PrecedenceClimbingParser.Call) token;
+ final List<SqlNode> list = new ArrayList<>();
+ for (PrecedenceClimbingParser.Token arg : call.args) {
+ list.add(convert(arg));
+ }
+ final ToTreeListItem item = (ToTreeListItem) call.op.o;
+ if (item.op == SqlStdOperatorTable.UNARY_MINUS
+ && list.size() == 1
+ && list.get(0) instanceof SqlNumericLiteral) {
+ return SqlLiteral.createNegative((SqlNumericLiteral) list.get(0),
+ item.pos.plusAll(list));
+ }
+ if (item.op == SqlStdOperatorTable.UNARY_PLUS
+ && list.size() == 1
+ && list.get(0) instanceof SqlNumericLiteral) {
+ return list.get(0);
+ }
+ return item.op.createCall(item.pos.plusAll(list), list);
+ default:
+ throw new AssertionError(token);
}
- return (SqlNode) list.get(start);
}
/**
@@ -810,6 +705,10 @@ public final class SqlParserUtil {
this.pos = pos;
}
+ public String toString() {
+ return op.toString();
+ }
+
public SqlOperator getOperator() {
return op;
}
@@ -834,6 +733,146 @@ public final class SqlParserUtil {
this.pos = pos;
}
}
+
+ /** Implementation of {@link SqlSpecialOperator.TokenSequence} based on an
+ * existing parser. */
+ private static class TokenSequenceImpl
+ implements SqlSpecialOperator.TokenSequence {
+ final List<PrecedenceClimbingParser.Token> list;
+ final PrecedenceClimbingParser parser;
+
+ private TokenSequenceImpl(PrecedenceClimbingParser parser) {
+ this.parser = parser;
+ this.list = parser.all();
+ }
+
+ public PrecedenceClimbingParser parser(int start, Predicate
+ <PrecedenceClimbingParser.Token> predicate) {
+ return parser.copy(start, predicate);
+ }
+
+ public int size() {
+ return list.size();
+ }
+
+ public SqlOperator op(int i) {
+ return ((ToTreeListItem) list.get(i).o).getOperator();
+ }
+
+ private static SqlParserPos pos(PrecedenceClimbingParser.Token token) {
+ switch (token.type) {
+ case ATOM:
+ return ((SqlNode) token.o).getParserPosition();
+ case CALL:
+ final PrecedenceClimbingParser.Call call =
+ (PrecedenceClimbingParser.Call) token;
+ SqlParserPos pos = ((ToTreeListItem) call.op.o).pos;
+ for (PrecedenceClimbingParser.Token arg : call.args) {
+ pos = pos.plus(pos(arg));
+ }
+ return pos;
+ default:
+ return ((ToTreeListItem) token.o).getPos();
+ }
+ }
+
+ public SqlParserPos pos(int i) {
+ return pos(list.get(i));
+ }
+
+ public boolean isOp(int i) {
+ return list.get(i).o instanceof ToTreeListItem;
+ }
+
+ public SqlNode node(int i) {
+ return convert(list.get(i));
+ }
+
+ public void replaceSublist(int start, int end, SqlNode e) {
+ SqlParserUtil.replaceSublist(list, start, end, parser.atom(e));
+ }
+ }
+
+ /** Implementation of {@link SqlSpecialOperator.TokenSequence}. */
+ private static class OldTokenSequenceImpl
+ implements SqlSpecialOperator.TokenSequence {
+ final List<Object> list;
+
+ private OldTokenSequenceImpl(List<Object> list) {
+ this.list = list;
+ }
+
+ @Override public PrecedenceClimbingParser parser(int start,
+ Predicate<PrecedenceClimbingParser.Token> predicate) {
+ final PrecedenceClimbingParser.Builder builder =
+ new PrecedenceClimbingParser.Builder();
+ for (Object o : Util.skip(list, start)) {
+ if (o instanceof ToTreeListItem) {
+ final ToTreeListItem item = (ToTreeListItem) o;
+ final SqlOperator op = item.getOperator();
+ if (op instanceof SqlPrefixOperator) {
+ builder.prefix(item, op.getLeftPrec());
+ } else if (op instanceof SqlPostfixOperator) {
+ builder.postfix(item, op.getRightPrec());
+ } else if (op instanceof SqlBinaryOperator) {
+ builder.infix(item, op.getLeftPrec(),
+ op.getLeftPrec() < op.getRightPrec());
+ } else if (op instanceof SqlSpecialOperator) {
+ builder.special(item, op.getLeftPrec(), op.getRightPrec(),
+ new PrecedenceClimbingParser.Special() {
+ public PrecedenceClimbingParser.Result apply(
+ PrecedenceClimbingParser parser,
+ PrecedenceClimbingParser.SpecialOp op) {
+ final List<PrecedenceClimbingParser.Token> tokens =
+ parser.all();
+ final SqlSpecialOperator op1 =
+ (SqlSpecialOperator) ((ToTreeListItem) op.o).op;
+ SqlSpecialOperator.ReduceResult r =
+ op1.reduceExpr(tokens.indexOf(op),
+ new TokenSequenceImpl(parser));
+ return new PrecedenceClimbingParser.Result(
+ tokens.get(r.startOrdinal),
+ tokens.get(r.endOrdinal - 1),
+ parser.atom(r.node));
+ }
+ });
+ } else {
+ throw new AssertionError();
+ }
+ } else {
+ builder.atom(o);
+ }
+ }
+ return builder.build();
+ }
+
+ public int size() {
+ return list.size();
+ }
+
+ public SqlOperator op(int i) {
+ return ((ToTreeListItem) list.get(i)).op;
+ }
+
+ public SqlParserPos pos(int i) {
+ final Object o = list.get(i);
+ return o instanceof ToTreeListItem
+ ? ((ToTreeListItem) o).pos
+ : ((SqlNode) o).getParserPosition();
+ }
+
+ public boolean isOp(int i) {
+ return list.get(i) instanceof ToTreeListItem;
+ }
+
+ public SqlNode node(int i) {
+ return (SqlNode) list.get(i);
+ }
+
+ public void replaceSublist(int start, int end, SqlNode e) {
+ SqlParserUtil.replaceSublist(list, start, end, e);
+ }
+ }
}
// End SqlParserUtil.java
http://git-wip-us.apache.org/repos/asf/calcite/blob/339ac13e/core/src/main/java/org/apache/calcite/util/PrecedenceClimbingParser.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/util/PrecedenceClimbingParser.java b/core/src/main/java/org/apache/calcite/util/PrecedenceClimbingParser.java
new file mode 100644
index 0000000..68b8be5
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/util/PrecedenceClimbingParser.java
@@ -0,0 +1,447 @@
+/*
+ * 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.util;
+
+import org.apache.calcite.linq4j.Ord;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Parser that takes a collection of tokens (atoms and operators)
+ * and groups them together according to the operators' precedence
+ * and associativity.
+ */
+public class PrecedenceClimbingParser {
+ private Token first;
+ private Token last;
+
+ private PrecedenceClimbingParser(List<Token> tokens) {
+ Token p = null;
+ for (Token token : tokens) {
+ if (p != null) {
+ p.next = token;
+ } else {
+ first = token;
+ }
+ token.previous = p;
+ token.next = null;
+ p = token;
+ }
+ last = p;
+ }
+
+ public Token atom(Object o) {
+ return new Token(Type.ATOM, o, -1, -1);
+ }
+
+ public Call call(Op op, ImmutableList<Token> args) {
+ return new Call(op, args);
+ }
+
+ public Op infix(Object o, int precedence, boolean left) {
+ return new Op(Type.INFIX, o, precedence * 2 + (left ? 0 : 1),
+ precedence * 2 + (left ? 1 : 0));
+ }
+
+ public Op prefix(Object o, int precedence) {
+ return new Op(Type.PREFIX, o, -1, precedence * 2);
+ }
+
+ public Op postfix(Object o, int precedence) {
+ return new Op(Type.POSTFIX, o, precedence * 2, -1);
+ }
+
+ public SpecialOp special(Object o, int leftPrec, int rightPrec,
+ Special special) {
+ return new SpecialOp(o, leftPrec * 2, rightPrec * 2, special);
+ }
+
+ public Token parse() {
+ partialParse();
+ if (first != last) {
+ throw new AssertionError("could not find next operator to reduce: "
+ + this);
+ }
+ return first;
+ }
+
+ public void partialParse() {
+ for (;;) {
+ Op op = highest();
+ if (op == null) {
+ return;
+ }
+ final Token t;
+ switch (op.type) {
+ case POSTFIX:
+ t = call(op, ImmutableList.of(op.previous));
+ replace(t, op.previous.previous, op.next);
+ break;
+ case PREFIX:
+ t = call(op, ImmutableList.of(op.next));
+ replace(t, op.previous, op.next.next);
+ break;
+ case INFIX:
+ t = call(op, ImmutableList.of(op.previous, op.next));
+ replace(t, op.previous.previous, op.next.next);
+ break;
+ case SPECIAL:
+ Result r = ((SpecialOp) op).special.apply(this, (SpecialOp) op);
+ Preconditions.checkNotNull(r);
+ replace(r.replacement, r.first.previous, r.last.next);
+ break;
+ default:
+ throw new AssertionError();
+ }
+ // debug: System.out.println(this);
+ }
+ }
+
+ @Override public String toString() {
+ return Util.commaList(all());
+ }
+
+ /** Returns a list of all tokens. */
+ public List<Token> all() {
+ return new TokenList();
+ }
+
+ private void replace(Token t, Token previous, Token next) {
+ t.previous = previous;
+ t.next = next;
+ if (previous == null) {
+ first = t;
+ } else {
+ previous.next = t;
+ }
+ if (next == null) {
+ last = t;
+ } else {
+ next.previous = t;
+ }
+ }
+
+ private Op highest() {
+ int p = -1;
+ Op highest = null;
+ for (Token t = first; t != null; t = t.next) {
+ if ((t.left > p || t.right > p)
+ && (t.left < 0 || t.left >= prevRight(t.previous))
+ && (t.right < 0 || t.right >= nextLeft(t.next))) {
+ p = Math.max(t.left, t.right);
+ highest = (Op) t;
+ }
+ }
+ return highest;
+ }
+
+ /** Returns the right precedence of the preceding operator token. */
+ private int prevRight(Token token) {
+ for (; token != null; token = token.previous) {
+ if (token.type == Type.POSTFIX) {
+ return Integer.MAX_VALUE;
+ }
+ if (token.right >= 0) {
+ return token.right;
+ }
+ }
+ return -1;
+ }
+
+ /** Returns the left precedence of the following operator token. */
+ private int nextLeft(Token token) {
+ for (; token != null; token = token.next) {
+ if (token.type == Type.PREFIX) {
+ return Integer.MAX_VALUE;
+ }
+ if (token.left >= 0) {
+ return token.left;
+ }
+ }
+ return -1;
+ }
+
+ public String print(Token token) {
+ return token.toString();
+ }
+
+ public PrecedenceClimbingParser copy(int start, Predicate<Token> predicate) {
+ final List<Token> tokens = new ArrayList<>();
+ for (Token token : Util.skip(all(), start)) {
+ if (predicate.apply(token)) {
+ break;
+ }
+ tokens.add(token.copy());
+ }
+ return new PrecedenceClimbingParser(tokens);
+ }
+
+ /** Token type. */
+ public enum Type {
+ ATOM,
+ CALL,
+ PREFIX,
+ INFIX,
+ POSTFIX,
+ SPECIAL
+ }
+
+ /** A token: either an atom, a call to an operator with arguments,
+ * or an unmatched operator. */
+ public static class Token {
+ Token previous;
+ Token next;
+ public final Type type;
+ public final Object o;
+ final int left;
+ final int right;
+
+ Token(Type type, Object o, int left, int right) {
+ this.type = type;
+ this.o = o;
+ this.left = left;
+ this.right = right;
+ }
+
+ @Override public String toString() {
+ return o.toString();
+ }
+
+ protected StringBuilder print(StringBuilder b) {
+ return b.append(o);
+ }
+
+ public Token copy() {
+ return new Token(type, o, left, right);
+ }
+ }
+
+ /** An operator token. */
+ public static class Op extends Token {
+ Op(Type type, Object o, int left, int right) {
+ super(type, o, left, right);
+ }
+
+ @Override public Token copy() {
+ return new Op(type, o, left, right);
+ }
+ }
+
+ /** An token corresponding to a special operator. */
+ public static class SpecialOp extends Op {
+ public final Special special;
+
+ SpecialOp(Object o, int left, int right, Special special) {
+ super(Type.SPECIAL, o, left, right);
+ this.special = special;
+ }
+
+ @Override public Token copy() {
+ return new SpecialOp(o, left, right, special);
+ }
+ }
+
+
+ /** A token that is a call to an operator with arguments. */
+ public static class Call extends Token {
+ public final Op op;
+ public final ImmutableList<Token> args;
+
+ Call(Op op, ImmutableList<Token> args) {
+ super(Type.CALL, null, -1, -1);
+ this.op = op;
+ this.args = args;
+ }
+
+ @Override public Token copy() {
+ return new Call(op, args);
+ }
+
+ @Override public String toString() {
+ return print(new StringBuilder()).toString();
+ }
+
+ protected StringBuilder print(StringBuilder b) {
+ switch (op.type) {
+ case PREFIX:
+ b.append('(');
+ printOp(b, false, true);
+ args.get(0).print(b);
+ return b.append(')');
+ case POSTFIX:
+ b.append('(');
+ args.get(0).print(b);
+ return printOp(b, true, false).append(')');
+ case INFIX:
+ b.append('(');
+ args.get(0).print(b);
+ printOp(b, true, true);
+ args.get(1).print(b);
+ return b.append(')');
+ case SPECIAL:
+ printOp(b, false, false)
+ .append('(');
+ for (Ord<Token> arg : Ord.zip(args)) {
+ if (arg.i > 0) {
+ b.append(", ");
+ }
+ arg.e.print(b);
+ }
+ return b.append(')');
+ default:
+ throw new AssertionError();
+ }
+ }
+
+ private StringBuilder printOp(StringBuilder b, boolean leftSpace,
+ boolean rightSpace) {
+ String s = op.o.toString();
+ if (leftSpace) {
+ b.append(' ');
+ }
+ b.append(s);
+ if (rightSpace) {
+ b.append(' ');
+ }
+ return b;
+ }
+ }
+
+ /** Callback defining the behavior of a special function. */
+ public interface Special {
+ /** Given an occurrence of this operator, identifies the range of tokens to
+ * be collapsed into a call of this operator, and the arguments to that
+ * call. */
+ Result apply(PrecedenceClimbingParser parser, SpecialOp op);
+ }
+
+ /** Result of a call to {@link Special#apply}. */
+ public static class Result {
+ final Token first;
+ final Token last;
+ final Token replacement;
+
+ public Result(Token first, Token last, Token replacement) {
+ this.first = first;
+ this.last = last;
+ this.replacement = replacement;
+ }
+ }
+
+ /** Fluent helper to build a parser containing a list of tokens. */
+ public static class Builder {
+ final List<Token> tokens = new ArrayList<>();
+ private final PrecedenceClimbingParser dummy =
+ new PrecedenceClimbingParser(ImmutableList.<Token>of());
+
+ private Builder add(Token t) {
+ tokens.add(t);
+ return this;
+ }
+
+ public Builder atom(Object o) {
+ return add(dummy.atom(o));
+ }
+
+ public Builder call(Op op, Token arg0, Token arg1) {
+ return add(dummy.call(op, ImmutableList.of(arg0, arg1)));
+ }
+
+ public Builder infix(Object o, int precedence, boolean left) {
+ return add(dummy.infix(o, precedence, left));
+ }
+
+ public Builder prefix(Object o, int precedence) {
+ return add(dummy.prefix(o, precedence));
+ }
+
+ public Builder postfix(Object o, int precedence) {
+ return add(dummy.postfix(o, precedence));
+ }
+
+ public Builder special(Object o, int leftPrec, int rightPrec,
+ Special special) {
+ return add(dummy.special(o, leftPrec, rightPrec, special));
+ }
+
+ public PrecedenceClimbingParser build() {
+ return new PrecedenceClimbingParser(tokens);
+ }
+ }
+
+ /** List view onto the tokens in a parser. The view is semi-mutable; it
+ * supports {@link List#remove(int)} but not {@link List#set} or
+ * {@link List#add}. */
+ private class TokenList extends AbstractList<Token> {
+ @Override public Token get(int index) {
+ for (Token t = first; t != null; t = t.next) {
+ if (index-- == 0) {
+ return t;
+ }
+ }
+ throw new IndexOutOfBoundsException();
+ }
+
+ @Override public int size() {
+ int n = 0;
+ for (Token t = first; t != null; t = t.next) {
+ ++n;
+ }
+ return n;
+ }
+
+ @Override public Token remove(int index) {
+ Token t = get(index);
+ if (t.previous == null) {
+ first = t.next;
+ } else {
+ t.previous.next = t.next;
+ }
+ if (t.next == null) {
+ last = t.previous;
+ } else {
+ t.next.previous = t.previous;
+ }
+ return t;
+ }
+
+ @Override public Token set(int index, Token element) {
+ final Token t = get(index);
+ element.previous = t.previous;
+ if (t.previous == null) {
+ first = element;
+ } else {
+ t.previous.next = element;
+ }
+ element.next = t.next;
+ if (t.next == null) {
+ last = element;
+ } else {
+ t.next.previous = element;
+ }
+ return t;
+ }
+ }
+}
+
+// End PrecedenceClimbingParser.java
http://git-wip-us.apache.org/repos/asf/calcite/blob/339ac13e/core/src/main/java/org/apache/calcite/util/Util.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/util/Util.java b/core/src/main/java/org/apache/calcite/util/Util.java
index 0eee37b..c01c772 100644
--- a/core/src/main/java/org/apache/calcite/util/Util.java
+++ b/core/src/main/java/org/apache/calcite/util/Util.java
@@ -2044,7 +2044,7 @@ public class Util {
/** Returns all but the first {@code n} elements of a list. */
public static <E> List<E> skip(List<E> list, int fromIndex) {
- return list.subList(fromIndex, list.size());
+ return fromIndex == 0 ? list : list.subList(fromIndex, list.size());
}
public static List<Integer> range(final int end) {
http://git-wip-us.apache.org/repos/asf/calcite/blob/339ac13e/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java
----------------------------------------------------------------------
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 590f0ee..7a3ea1a 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
@@ -744,6 +744,56 @@ public class SqlParserTest {
+ "WHERE (1 AND TRUE)");
}
+ @Test public void testLessThanAssociativity() {
+ checkExp("NOT a = b", "(NOT (`A` = `B`))");
+
+ // comparison operators are left-associative
+ checkExp("x < y < z", "((`X` < `Y`) < `Z`)");
+ checkExp("x < y <= z = a", "(((`X` < `Y`) <= `Z`) = `A`)");
+ checkExp("a = x < y <= z = a", "((((`A` = `X`) < `Y`) <= `Z`) = `A`)");
+
+ // IS NULL has lower precedence than comparison
+ checkExp("a = x is null", "((`A` = `X`) IS NULL)");
+ checkExp("a = x is not null", "((`A` = `X`) IS NOT NULL)");
+
+ // BETWEEN, IN, LIKE have higher precedence than comparison
+ checkExp("a = x between y = b and z = c",
+ "((`A` = (`X` BETWEEN ASYMMETRIC (`Y` = `B`) AND `Z`)) = `C`)");
+ checkExp("a = x like y = b",
+ "((`A` = (`X` LIKE `Y`)) = `B`)");
+ checkExp("a = x not like y = b",
+ "((`A` = (`X` NOT LIKE `Y`)) = `B`)");
+ checkExp("a = x similar to y = b",
+ "((`A` = (`X` SIMILAR TO `Y`)) = `B`)");
+ checkExp("a = x not similar to y = b",
+ "((`A` = (`X` NOT SIMILAR TO `Y`)) = `B`)");
+ checkExp("a = x not in (y, z) = b",
+ "((`A` = (`X` NOT IN (`Y`, `Z`))) = `B`)");
+
+ // LIKE has higher precedence than IS NULL
+ checkExp("a like b is null", "((`A` LIKE `B`) IS NULL)");
+ checkExp("a not like b is not null",
+ "((`A` NOT LIKE `B`) IS NOT NULL)");
+
+ // = has higher precedence than NOT
+ checkExp("NOT a = b", "(NOT (`A` = `B`))");
+ checkExp("NOT a = NOT b", "(NOT (`A` = (NOT `B`)))");
+
+ // IS NULL has higher precedence than NOT
+ checkExp("NOT a IS NULL", "(NOT (`A` IS NULL))");
+ checkExp("NOT a = b IS NOT NULL", "(NOT ((`A` = `B`) IS NOT NULL))");
+
+ // NOT has higher precedence than AND, which has higher precedence than OR
+ checkExp("NOT a AND NOT b", "((NOT `A`) AND (NOT `B`))");
+ checkExp("NOT a OR NOT b", "((NOT `A`) OR (NOT `B`))");
+ checkExp("NOT a = b AND NOT c = d OR NOT e = f",
+ "(((NOT (`A` = `B`)) AND (NOT (`C` = `D`))) OR (NOT (`E` = `F`)))");
+ checkExp("NOT a = b OR NOT c = d AND NOT e = f",
+ "((NOT (`A` = `B`)) OR ((NOT (`C` = `D`)) AND (NOT (`E` = `F`))))");
+ checkExp("NOT NOT a = b OR NOT NOT c = d",
+ "((NOT (NOT (`A` = `B`))) OR (NOT (NOT (`C` = `D`))))");
+ }
+
@Test public void testIsBooleans() {
String[] inOuts = {"NULL", "TRUE", "FALSE", "UNKNOWN"};
@@ -752,7 +802,7 @@ public class SqlParserTest {
"select * from t where nOt fAlSe Is " + inOut,
"SELECT *\n"
+ "FROM `T`\n"
- + "WHERE ((NOT FALSE) IS " + inOut + ")");
+ + "WHERE (NOT (FALSE IS " + inOut + "))");
check(
"select * from t where c1=1.1 IS NOT " + inOut,
@@ -771,7 +821,7 @@ public class SqlParserTest {
check("select 1 from t where not true is unknown",
"SELECT 1\n"
+ "FROM `T`\n"
- + "WHERE ((NOT TRUE) IS UNKNOWN)");
+ + "WHERE (NOT (TRUE IS UNKNOWN))");
check(
"select * from t where x is unknown is not unknown is false is not false"
@@ -781,11 +831,16 @@ public class SqlParserTest {
+ "WHERE ((((((((`X` IS UNKNOWN) IS NOT UNKNOWN) IS FALSE) IS NOT FALSE) IS TRUE) IS NOT TRUE) IS NULL) IS NOT NULL)");
// combine IS postfix operators with infix (AND) and prefix (NOT) ops
- check(
- "select * from t where x is unknown is false and x is unknown is true or not y is unknown is not null",
- "SELECT *\n"
- + "FROM `T`\n"
- + "WHERE ((((`X` IS UNKNOWN) IS FALSE) AND ((`X` IS UNKNOWN) IS TRUE)) OR (((NOT `Y`) IS UNKNOWN) IS NOT NULL))");
+ final String sql = "select * from t "
+ + "where x is unknown is false "
+ + "and x is unknown is true "
+ + "or not y is unknown is not null";
+ final String expected = "SELECT *\n"
+ + "FROM `T`\n"
+ + "WHERE ((((`X` IS UNKNOWN) IS FALSE)"
+ + " AND ((`X` IS UNKNOWN) IS TRUE))"
+ + " OR (NOT ((`Y` IS UNKNOWN) IS NOT NULL)))";
+ check(sql, expected);
}
@Test public void testEqualNotEqual() {
@@ -871,10 +926,10 @@ public class SqlParserTest {
"values a between b and c + 2 or d and e",
"VALUES (ROW(((`A` BETWEEN ASYMMETRIC `B` AND (`C` + 2)) OR (`D` AND `E`))))");
- // '=' and BETWEEN have same precedence, and are left-assoc
+ // '=' has slightly lower precedence than BETWEEN; both are left-assoc
check(
"values x = a between b and c = d = e",
- "VALUES (ROW(((((`X` = `A`) BETWEEN ASYMMETRIC `B` AND `C`) = `D`) = `E`)))");
+ "VALUES (ROW((((`X` = (`A` BETWEEN ASYMMETRIC `B` AND `C`)) = `D`) = `E`)))");
// AND doesn't match BETWEEN if it's between parentheses!
check(
@@ -1147,7 +1202,7 @@ public class SqlParserTest {
// LIKE has same precedence as '='; LIKE is right-assoc, '=' is left
check(
"values a = b like c = d",
- "VALUES (ROW(((`A` = `B`) LIKE (`C` = `D`))))");
+ "VALUES (ROW(((`A` = (`B` LIKE `C`)) = `D`)))");
// Nested LIKE
check(
@@ -1228,13 +1283,13 @@ public class SqlParserTest {
@Test public void testFoo() {
}
- @Test public void testArthimeticOperators() {
+ @Test public void testArithmeticOperators() {
checkExp("1-2+3*4/5/6-7", "(((1 - 2) + (((3 * 4) / 5) / 6)) - 7)");
checkExp("power(2,3)", "POWER(2, 3)");
checkExp("aBs(-2.3e-2)", "ABS(-2.3E-2)");
checkExp("MOD(5 ,\t\f\r\n2)", "MOD(5, 2)");
checkExp("ln(5.43 )", "LN(5.43)");
- checkExp("log10(- -.2 )", "LOG10((- -0.2))");
+ checkExp("log10(- -.2 )", "LOG10(0.2)");
}
@Test public void testExists() {
@@ -2297,14 +2352,14 @@ public class SqlParserTest {
check(
"values (- -1\n"
+ ")",
- "VALUES (ROW((- -1)))");
+ "VALUES (ROW(1))");
check(
"values (--1+\n"
+ "2)",
"VALUES (ROW(2))");
- // end of multiline commment without start
+ // end of multiline comment without start
if (Bug.FRG73_FIXED) {
checkFails("values (1 */ 2)", "xx");
}
@@ -2380,7 +2435,7 @@ public class SqlParserTest {
checkExp("1", "1");
checkExp("+1.", "1");
checkExp("-1", "-1");
- checkExp("- -1", "(- -1)");
+ checkExp("- -1", "1");
checkExp("1.0", "1.0");
checkExp("-3.2", "-3.2");
checkExp("1.", "1");
@@ -2437,7 +2492,11 @@ public class SqlParserTest {
}
@Test public void testPrecedence2() {
- checkExp("- - 1", "(- -1)"); // two prefices
+ checkExp("- - 1", "1"); // special case for unary minus
+ }
+
+ @Test public void testPrecedence2b() {
+ checkExp("not not 1", "(NOT (NOT 1))"); // two prefixes
}
@Test public void testPrecedence3() {
@@ -2821,7 +2880,7 @@ public class SqlParserTest {
"select ^cursor^(select * from emps) from emps",
"CURSOR expression encountered in illegal context");
checkFails(
- "call p(^cursor^(select * from emps))",
+ "call list(^cursor^(select * from emps))",
"CURSOR expression encountered in illegal context");
checkFails(
"select f(^cursor^(select * from emps)) from emps",
@@ -6887,11 +6946,7 @@ public class SqlParserTest {
try {
sqlNode = parseStmt(sql);
} catch (SqlParseException e) {
- e.printStackTrace();
- String message = "Received error while parsing SQL '" + sql
- + "'; error is:\n"
- + e.toString();
- throw new AssertionError(message);
+ throw new RuntimeException("Error while parsing SQL: " + sql, e);
}
return sqlNode;
}
@@ -6912,10 +6967,7 @@ public class SqlParserTest {
try {
sqlNode = parseExpression(sql);
} catch (SqlParseException e) {
- String message = "Received error while parsing SQL '" + sql
- + "'; error is:\n"
- + e.toString();
- throw new RuntimeException(message, e);
+ throw new RuntimeException("Error while parsing expression: " + sql, e);
}
return sqlNode;
}
http://git-wip-us.apache.org/repos/asf/calcite/blob/339ac13e/core/src/test/java/org/apache/calcite/sql/test/SqlTesterImpl.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/sql/test/SqlTesterImpl.java b/core/src/test/java/org/apache/calcite/sql/test/SqlTesterImpl.java
index f9f1bd1..3c55d6d 100644
--- a/core/src/test/java/org/apache/calcite/sql/test/SqlTesterImpl.java
+++ b/core/src/test/java/org/apache/calcite/sql/test/SqlTesterImpl.java
@@ -113,23 +113,16 @@ public class SqlTesterImpl implements SqlTester {
} catch (SqlParseException e) {
String errMessage = e.getMessage();
if (expectedMsgPattern == null) {
- e.printStackTrace();
- throw new AssertionError(
- "Error while parsing query [" + sap.sql + "]");
- } else if (
- (null == errMessage)
- || !errMessage.matches(expectedMsgPattern)) {
- e.printStackTrace();
- throw new AssertionError(
- "Error did not match expected ["
- + expectedMsgPattern + "] while parsing query ["
- + sap.sql + "]");
+ throw new RuntimeException("Error while parsing query:" + sap.sql, e);
+ } else if (errMessage == null
+ || !errMessage.matches(expectedMsgPattern)) {
+ throw new RuntimeException("Error did not match expected ["
+ + expectedMsgPattern + "] while parsing query ["
+ + sap.sql + "]", e);
}
return;
} catch (Throwable e) {
- e.printStackTrace();
- throw new AssertionError(
- "Error while parsing query [" + sap.sql + "]");
+ throw new RuntimeException("Error while parsing query: " + sap.sql, e);
}
Throwable thrown = null;
@@ -163,11 +156,8 @@ public class SqlTesterImpl implements SqlTester {
SqlNode sqlNode;
try {
sqlNode = parseQuery(sql);
- } catch (SqlParseException e) {
- throw new RuntimeException("Error while parsing query [" + sql + "]", e);
} catch (Throwable e) {
- e.printStackTrace();
- throw new AssertionError("Error while parsing query [" + sql + "]");
+ throw new RuntimeException("Error while parsing query: " + sql, e);
}
return validator.validate(sqlNode);
}
http://git-wip-us.apache.org/repos/asf/calcite/blob/339ac13e/core/src/test/java/org/apache/calcite/test/CalciteSuite.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/CalciteSuite.java b/core/src/test/java/org/apache/calcite/test/CalciteSuite.java
index 7211e05..7152553 100644
--- a/core/src/test/java/org/apache/calcite/test/CalciteSuite.java
+++ b/core/src/test/java/org/apache/calcite/test/CalciteSuite.java
@@ -53,6 +53,7 @@ import org.apache.calcite.util.ChunkListTest;
import org.apache.calcite.util.ImmutableBitSetTest;
import org.apache.calcite.util.PartiallyOrderedSetTest;
import org.apache.calcite.util.PermutationTestCase;
+import org.apache.calcite.util.PrecedenceClimbingParserTest;
import org.apache.calcite.util.ReflectVisitorTest;
import org.apache.calcite.util.UtilTest;
import org.apache.calcite.util.graph.DirectedGraphTest;
@@ -81,6 +82,7 @@ import org.junit.runners.Suite;
RelOptUtilTest.class,
RelCollationTest.class,
UtilTest.class,
+ PrecedenceClimbingParserTest.class,
MappingTest.class,
CalciteResourceTest.class,
FilteratorTest.class,
http://git-wip-us.apache.org/repos/asf/calcite/blob/339ac13e/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
index 5d2ef4a..7fff2d9 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
@@ -23,6 +23,7 @@ import org.apache.calcite.config.Lex;
import org.apache.calcite.rel.type.RelDataTypeSystem;
import org.apache.calcite.sql.SqlCollation;
import org.apache.calcite.sql.SqlOperator;
+import org.apache.calcite.sql.SqlSpecialOperator;
import org.apache.calcite.sql.fun.OracleSqlOperatorTable;
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.sql.test.SqlTester;
@@ -37,6 +38,7 @@ import org.apache.calcite.util.ImmutableBitSet;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Ordering;
import org.junit.BeforeClass;
import org.junit.Ignore;
@@ -46,6 +48,7 @@ import org.slf4j.LoggerFactory;
import java.nio.charset.Charset;
import java.util.Arrays;
+import java.util.Comparator;
import java.util.List;
import java.util.Locale;
@@ -7623,6 +7626,184 @@ public class SqlValidatorTest extends SqlValidatorTestCase {
}
}
+ private static int prec(SqlOperator op) {
+ return Math.max(op.getLeftPrec(), op.getRightPrec());
+ }
+
+ /** Tests that operators, sorted by precedence, are in a sane order. Each
+ * operator has a {@link SqlOperator#getLeftPrec() left} and
+ * {@link SqlOperator#getRightPrec()} right} precedence, but we would like
+ * the order to remain the same even if we tweak particular operators'
+ * precedences. If you need to update the expected output, you might also
+ * need to change
+ * <a href="http://calcite.apache.org/docs/reference.html#operator-precedence">
+ * the documentation</a>. */
+ @Test public void testOperatorsSortedByPrecedence() {
+ final StringBuilder b = new StringBuilder();
+ final Comparator<SqlOperator> comparator =
+ new Comparator<SqlOperator>() {
+ public int compare(SqlOperator o1, SqlOperator o2) {
+ int c = Integer.compare(prec(o1), prec(o2));
+ if (c != 0) {
+ return -c;
+ }
+ c = o1.getName().compareTo(o2.getName());
+ if (c != 0) {
+ return c;
+ }
+ return o1.getSyntax().compareTo(o2.getSyntax());
+ }
+ };
+ final List<SqlOperator> operators =
+ SqlStdOperatorTable.instance().getOperatorList();
+ int p = -1;
+ for (SqlOperator op : Ordering.from(comparator).sortedCopy(operators)) {
+ final String type;
+ switch (op.getSyntax()) {
+ case FUNCTION:
+ case FUNCTION_ID:
+ case FUNCTION_STAR:
+ case INTERNAL:
+ continue;
+ case PREFIX:
+ type = "pre";
+ break;
+ case POSTFIX:
+ type = "post";
+ break;
+ case BINARY:
+ if (op.getLeftPrec() < op.getRightPrec()) {
+ type = "left";
+ } else {
+ type = "right";
+ }
+ break;
+ default:
+ if (op instanceof SqlSpecialOperator) {
+ type = "-";
+ } else {
+ continue;
+ }
+ }
+ if (prec(op) != p) {
+ b.append('\n');
+ p = prec(op);
+ }
+ b.append(op.getName())
+ .append(' ')
+ .append(type)
+ .append('\n');
+ }
+ final String expected = "\n"
+ + "ARRAY -\n"
+ + "ARRAY -\n"
+ + "COLUMN_LIST -\n"
+ + "CURSOR -\n"
+ + "LATERAL -\n"
+ + "MAP -\n"
+ + "MAP -\n"
+ + "MULTISET -\n"
+ + "MULTISET -\n"
+ + "ROW -\n"
+ + "TABLE -\n"
+ + "UNNEST -\n"
+ + "\n"
+ + "CURRENT_VALUE -\n"
+ + "DEFAULT -\n"
+ + "ITEM -\n"
+ + "NEXT_VALUE -\n"
+ + "\n"
+ + "$LiteralChain -\n"
+ + "+ pre\n"
+ + "- pre\n"
+ + ". left\n"
+ + "\n"
+ + "* left\n"
+ + "/ left\n"
+ + "/INT left\n"
+ + "|| left\n"
+ + "\n"
+ + "+ left\n"
+ + "- left\n"
+ + "- -\n"
+ + "DATETIME_PLUS -\n"
+ + "EXISTS pre\n"
+ + "\n"
+ + "BETWEEN ASYMMETRIC -\n"
+ + "BETWEEN SYMMETRIC -\n"
+ + "IN left\n"
+ + "LIKE -\n"
+ + "NOT BETWEEN ASYMMETRIC -\n"
+ + "NOT BETWEEN SYMMETRIC -\n"
+ + "NOT IN left\n"
+ + "NOT LIKE -\n"
+ + "NOT SIMILAR TO -\n"
+ + "SIMILAR TO -\n"
+ + "\n"
+ + "$IS_DIFFERENT_FROM left\n"
+ + "< left\n"
+ + "<= left\n"
+ + "<> left\n"
+ + "= left\n"
+ + "> left\n"
+ + ">= left\n"
+ + "IS DISTINCT FROM left\n"
+ + "IS NOT DISTINCT FROM left\n"
+ + "MEMBER OF left\n"
+ + "OVERLAPS -\n"
+ + "SUBMULTISET OF left\n"
+ + "\n"
+ + "IS A SET post\n"
+ + "IS FALSE post\n"
+ + "IS NOT FALSE post\n"
+ + "IS NOT NULL post\n"
+ + "IS NOT TRUE post\n"
+ + "IS NOT UNKNOWN post\n"
+ + "IS NULL post\n"
+ + "IS TRUE post\n"
+ + "IS UNKNOWN post\n"
+ + "\n"
+ + "NOT pre\n"
+ + "\n"
+ + "AND left\n"
+ + "\n"
+ + "OR left\n"
+ + "\n"
+ + "=> -\n"
+ + "AS -\n"
+ + "DESC post\n"
+ + "OVER left\n"
+ + "TABLESAMPLE -\n"
+ + "\n"
+ + "INTERSECT left\n"
+ + "INTERSECT ALL left\n"
+ + "MULTISET INTERSECT left\n"
+ + "MULTISET INTERSECT ALL left\n"
+ + "NULLS FIRST post\n"
+ + "NULLS LAST post\n"
+ + "\n"
+ + "EXCEPT left\n"
+ + "EXCEPT ALL left\n"
+ + "MULTISET EXCEPT left\n"
+ + "MULTISET EXCEPT ALL left\n"
+ + "MULTISET UNION left\n"
+ + "MULTISET UNION ALL left\n"
+ + "UNION left\n"
+ + "UNION ALL left\n"
+ + "\n"
+ + "$throw -\n"
+ + "EXTRACT_DATE -\n"
+ + "FILTER left\n"
+ + "Reinterpret -\n"
+ + "TABLE pre\n"
+ + "VALUES -\n"
+ + "\n"
+ + "CALL pre\n"
+ + "ESCAPE -\n"
+ + "NEW pre\n";
+ assertThat(b.toString(), is(expected));
+ }
+
/** Tests that it is an error to insert into the same column twice, even using
* case-insensitive matching. */
@Test public void testCaseInsensitiveInsert() {
http://git-wip-us.apache.org/repos/asf/calcite/blob/339ac13e/core/src/test/java/org/apache/calcite/util/PrecedenceClimbingParserTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/util/PrecedenceClimbingParserTest.java b/core/src/test/java/org/apache/calcite/util/PrecedenceClimbingParserTest.java
new file mode 100644
index 0000000..3fbeac4
--- /dev/null
+++ b/core/src/test/java/org/apache/calcite/util/PrecedenceClimbingParserTest.java
@@ -0,0 +1,181 @@
+/*
+ * 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.util;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Unit test for {@link PrecedenceClimbingParser}.
+ */
+public class PrecedenceClimbingParserTest {
+ @Test public void testBasic() {
+ final PrecedenceClimbingParser p = new PrecedenceClimbingParser.Builder()
+ .atom("a")
+ .infix("+", 1, true)
+ .prefix("-", 3)
+ .atom("b")
+ .infix("*", 2, true)
+ .atom("c")
+ .postfix("!", 4)
+ .build();
+ final PrecedenceClimbingParser.Token token = p.parse();
+ assertThat(p.print(token), is("(a + ((- b) * (c !)))"));
+ }
+
+ @Test public void testRepeatedPrefixPostfix() {
+ final PrecedenceClimbingParser p = new PrecedenceClimbingParser.Builder()
+ .prefix("+", 3)
+ .prefix("-", 3)
+ .prefix("+", 3)
+ .prefix("+", 3)
+ .atom("a")
+ .postfix("!", 4)
+ .infix("+", 1, true)
+ .prefix("-", 3)
+ .prefix("-", 3)
+ .atom("b")
+ .postfix("!", 4)
+ .postfix("!", 4)
+ .build();
+ final PrecedenceClimbingParser.Token token = p.parse();
+ assertThat(p.print(token),
+ is("((+ (- (+ (+ (a !))))) + (- (- ((b !) !))))"));
+ }
+
+ @Test public void testAtom() {
+ final PrecedenceClimbingParser p = new PrecedenceClimbingParser.Builder()
+ .atom("a")
+ .build();
+ final PrecedenceClimbingParser.Token token = p.parse();
+ assertThat(p.print(token), is("a"));
+ }
+
+ @Test public void testOnlyPrefix() {
+ final PrecedenceClimbingParser p = new PrecedenceClimbingParser.Builder()
+ .prefix("-", 3)
+ .prefix("-", 3)
+ .atom(1)
+ .build();
+ final PrecedenceClimbingParser.Token token = p.parse();
+ assertThat(p.print(token), is("(- (- 1))"));
+ }
+
+ @Test public void testOnlyPostfix() {
+ final PrecedenceClimbingParser p = new PrecedenceClimbingParser.Builder()
+ .atom(1)
+ .postfix("!", 33333)
+ .postfix("!", 33333)
+ .build();
+ final PrecedenceClimbingParser.Token token = p.parse();
+ assertThat(p.print(token), is("((1 !) !)"));
+ }
+
+ @Test public void testLeftAssociative() {
+ final PrecedenceClimbingParser p = new PrecedenceClimbingParser.Builder()
+ .atom("a")
+ .infix("*", 2, true)
+ .atom("b")
+ .infix("+", 1, true)
+ .atom("c")
+ .infix("+", 1, true)
+ .atom("d")
+ .infix("+", 1, true)
+ .atom("e")
+ .infix("*", 2, true)
+ .atom("f")
+ .build();
+ final PrecedenceClimbingParser.Token token = p.parse();
+ assertThat(p.print(token), is("((((a * b) + c) + d) + (e * f))"));
+ }
+
+ @Test public void testRightAssociative() {
+ final PrecedenceClimbingParser p = new PrecedenceClimbingParser.Builder()
+ .atom("a")
+ .infix("^", 3, false)
+ .atom("b")
+ .infix("^", 3, false)
+ .atom("c")
+ .infix("^", 3, false)
+ .atom("d")
+ .infix("+", 1, true)
+ .atom("e")
+ .infix("*", 2, true)
+ .atom("f")
+ .build();
+ final PrecedenceClimbingParser.Token token = p.parse();
+ assertThat(p.print(token), is("((a ^ (b ^ (c ^ d))) + (e * f))"));
+ }
+
+ @Test public void testSpecial() {
+ // price > 5 and price between 1 + 2 and 3 * 4 and price is null
+ final PrecedenceClimbingParser p = new PrecedenceClimbingParser.Builder()
+ .atom("price")
+ .infix(">", 4, true)
+ .atom("5")
+ .infix("and", 2, true)
+ .atom("price")
+ .special("between", 3, 3,
+ new PrecedenceClimbingParser.Special() {
+ public PrecedenceClimbingParser.Result apply(
+ PrecedenceClimbingParser parser,
+ PrecedenceClimbingParser.SpecialOp op) {
+ return new PrecedenceClimbingParser.Result(op.previous,
+ op.next.next.next,
+ parser.call(op,
+ ImmutableList.of(op.previous, op.next,
+ op.next.next.next)));
+ }
+ })
+ .atom("1")
+ .infix("+", 5, true)
+ .atom("2")
+ .infix("and", 2, true)
+ .atom("3")
+ .infix("*", 6, true)
+ .atom("4")
+ .infix("and", 2, true)
+ .atom("price")
+ .postfix("is null", 4)
+ .build();
+ final PrecedenceClimbingParser.Token token = p.parse();
+ assertThat(p.print(token),
+ is("(((price > 5) and between(price, (1 + 2), (3 * 4)))"
+ + " and (price is null))"));
+ }
+
+ @Test public void testEqualPrecedence() {
+ // LIKE has same precedence as '='; LIKE is right-assoc, '=' is left
+ final PrecedenceClimbingParser p = new PrecedenceClimbingParser.Builder()
+ .atom("a")
+ .infix("=", 3, true)
+ .atom("b")
+ .infix("like", 3, false)
+ .atom("c")
+ .infix("=", 3, true)
+ .atom("d")
+ .build();
+ final PrecedenceClimbingParser.Token token = p.parse();
+ assertThat(p.print(token), is("(((a = b) like c) = d)"));
+ }
+}
+
+// End PrecedenceClimbingParserTest.java
http://git-wip-us.apache.org/repos/asf/calcite/blob/339ac13e/core/src/test/resources/sql/operator.iq
----------------------------------------------------------------------
diff --git a/core/src/test/resources/sql/operator.iq b/core/src/test/resources/sql/operator.iq
new file mode 100644
index 0000000..626d31d
--- /dev/null
+++ b/core/src/test/resources/sql/operator.iq
@@ -0,0 +1,69 @@
+# operator.iq - expressions involving operators
+#
+# 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.
+#
+!use scott
+!set outputformat mysql
+
+# [CALCITE-1095] NOT precedence
+select * from "scott".emp where not sal > 1300;
++-------+--------+----------+------+------------+---------+---------+--------+
+| EMPNO | ENAME | JOB | MGR | HIREDATE | SAL | COMM | DEPTNO |
++-------+--------+----------+------+------------+---------+---------+--------+
+| 7369 | SMITH | CLERK | 7902 | 1980-12-17 | 800.00 | | 20 |
+| 7521 | WARD | SALESMAN | 7698 | 1981-02-22 | 1250.00 | 500.00 | 30 |
+| 7654 | MARTIN | SALESMAN | 7698 | 1981-09-28 | 1250.00 | 1400.00 | 30 |
+| 7876 | ADAMS | CLERK | 7788 | 1987-05-23 | 1100.00 | | 20 |
+| 7900 | JAMES | CLERK | 7698 | 1981-12-03 | 950.00 | | 30 |
+| 7934 | MILLER | CLERK | 7782 | 1982-01-23 | 1300.00 | | 10 |
++-------+--------+----------+------+------------+---------+---------+--------+
+(6 rows)
+
+!ok
+
+select count(*) as c from "scott".emp where not ename = 'SMITH';
++----+
+| C |
++----+
+| 13 |
++----+
+(1 row)
+
+!ok
+
+select count(*) as c from "scott".emp where not not ename = 'SMITH';
++---+
+| C |
++---+
+| 1 |
++---+
+(1 row)
+
+!ok
+
+select * from "scott".emp where not sal > 1300 and not sal < 1200;
++-------+--------+----------+------+------------+---------+---------+--------+
+| EMPNO | ENAME | JOB | MGR | HIREDATE | SAL | COMM | DEPTNO |
++-------+--------+----------+------+------------+---------+---------+--------+
+| 7521 | WARD | SALESMAN | 7698 | 1981-02-22 | 1250.00 | 500.00 | 30 |
+| 7654 | MARTIN | SALESMAN | 7698 | 1981-09-28 | 1250.00 | 1400.00 | 30 |
+| 7934 | MILLER | CLERK | 7782 | 1982-01-23 | 1300.00 | | 10 |
++-------+--------+----------+------+------------+---------+---------+--------+
+(3 rows)
+
+!ok
+
+# End operator.iq
http://git-wip-us.apache.org/repos/asf/calcite/blob/339ac13e/site/_docs/reference.md
----------------------------------------------------------------------
diff --git a/site/_docs/reference.md b/site/_docs/reference.md
index 8f9fd16..428ef83 100644
--- a/site/_docs/reference.md
+++ b/site/_docs/reference.md
@@ -831,6 +831,24 @@ Note:
## Operators and functions
+### Operator precedence
+
+The operator precedence and associativity, highest to lowest.
+
+| Operator | Associativity
+|:------------------------------------------------- |:-------------
+| . | left
+| [ ] (array element) | left
+| + - (unary plus, minus) | right
+| * / | left
+| + - | left
+| BETWEEN, IN, LIKE, SIMILAR | -
+| < > = <= >= <> | left
+| IS NULL, IS FALSE, IS NOT TRUE etc. | -
+| NOT | right
+| AND | left
+| OR | left
+
### Comparison operators
| Operator syntax | Description