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

mmior pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/calcite.git


The following commit(s) were added to refs/heads/master by this push:
     new 67b439f  [CALCITE-2786] Add order by clause support for JSON_ARRAYAGG
67b439f is described below

commit 67b439fc1d98fd0082517684b74b53cd1050bbf4
Author: Hongze Zhang <[email protected]>
AuthorDate: Sat Feb 23 01:52:39 2019 +0800

    [CALCITE-2786] Add order by clause support for JSON_ARRAYAGG
---
 core/src/main/codegen/templates/Parser.jj          | 72 ++++++++++++++++++----
 .../apache/calcite/runtime/CalciteResource.java    |  3 +
 .../sql/fun/SqlJsonArrayAggAggFunction.java        | 23 ++++++-
 .../calcite/runtime/CalciteResource.properties     |  1 +
 .../apache/calcite/sql/parser/SqlParserTest.java   | 12 +++-
 .../apache/calcite/test/SqlToRelConverterTest.java | 20 +++++-
 .../apache/calcite/test/SqlToRelConverterTest.xml  | 41 +++++++++++-
 core/src/test/resources/sql/agg.iq                 | 37 +++++++++++
 site/_docs/reference.md                            |  3 +-
 9 files changed, 194 insertions(+), 18 deletions(-)

diff --git a/core/src/main/codegen/templates/Parser.jj 
b/core/src/main/codegen/templates/Parser.jj
index a546495..07c8cf8 100644
--- a/core/src/main/codegen/templates/Parser.jj
+++ b/core/src/main/codegen/templates/Parser.jj
@@ -5323,28 +5323,64 @@ SqlCall JsonArrayFunctionCall() :
     }
 }
 
+SqlNodeList JsonArrayAggOrderByClause() :
+{
+    final SqlNodeList orderList;
+}
+{
+    (
+        orderList = OrderBy(true)
+    |
+        { orderList = null; }
+    )
+    {
+        return orderList;
+    }
+}
+
 SqlCall JsonArrayAggFunctionCall() :
 {
-    final SqlNode[] args = new SqlNode[1];
+    final SqlNode valueExpr;
+    SqlNodeList orderList = null;
     List<SqlNode> list;
     final Span span;
     SqlJsonConstructorNullClause nullClause =
         SqlJsonConstructorNullClause.ABSENT_ON_NULL;
-    SqlNode e;
+    SqlNode e = null;
+    final SqlNode aggCall;
 }
 {
     <JSON_ARRAYAGG> { span = span(); }
     <LPAREN> e = JsonValueExpression(false) {
-        args[0] = e;
+        valueExpr = e;
     }
+    orderList = JsonArrayAggOrderByClause()
     [
         e = JsonConstructorNullClause() {
             nullClause = (SqlJsonConstructorNullClause) ((SqlLiteral) 
e).getValue();
         }
     ]
-    <RPAREN> {
+    <RPAREN>
+    {
+        aggCall = SqlStdOperatorTable.JSON_ARRAYAGG.with(nullClause)
+            .createCall(span.end(this), valueExpr, orderList);
+    }
+    [
+        e = withinGroup(aggCall) {
+            if (orderList != null) {
+                throw 
SqlUtil.newContextException(span.pos().plus(e.getParserPosition()),
+                    RESOURCE.ambiguousSortOrderInJsonArrayAggFunc());
+            }
+            return (SqlCall) e;
+        }
+    ]
+    {
+        if (orderList == null) {
+            return SqlStdOperatorTable.JSON_ARRAYAGG.with(nullClause)
+                .createCall(span.end(this), valueExpr);
+        }
         return SqlStdOperatorTable.JSON_ARRAYAGG.with(nullClause)
-            .createCall(span.end(this), args);
+            .createCall(span.end(this), valueExpr, orderList);
     }
 }
 
@@ -5503,6 +5539,23 @@ SqlCall MatchRecognizeNavigationPhysical() :
     }
 }
 
+SqlCall withinGroup(SqlNode arg) :
+{
+    final Span withinGroupSpan;
+    final SqlNodeList orderList;
+}
+{
+
+    <WITHIN> { withinGroupSpan = span(); }
+    <GROUP>
+    <LPAREN>
+    orderList = OrderBy(true)
+    <RPAREN> {
+        return SqlStdOperatorTable.WITHIN_GROUP.createCall(
+            withinGroupSpan.end(this), arg, orderList);
+    }
+}
+
 /**
  * Parses a call to a named function (could be a builtin with regular
  * syntax, or else a UDF).
@@ -5562,14 +5615,7 @@ SqlNode NamedFunctionCall() :
         call = createCall(qualifiedName, s.end(this), funcType, quantifier, 
args);
     }
     [
-        <WITHIN> { withinGroupSpan = span(); }
-        <GROUP>
-        <LPAREN>
-        orderList = OrderBy(true)
-        <RPAREN> {
-            call = SqlStdOperatorTable.WITHIN_GROUP.createCall(
-                withinGroupSpan.end(this), call, orderList);
-        }
+        call = withinGroup(call)
     ]
     [
         <FILTER> { filterSpan = span(); }
diff --git a/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java 
b/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
index 6ae9bea..c9da744 100644
--- a/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
+++ b/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
@@ -850,6 +850,9 @@ public interface CalciteResource {
   @BaseMessage("Timeout of ''{0}'' ms for query execution is reached. Query 
execution started at ''{1}''")
   ExInst<CalciteException> queryExecutionTimeoutReached(String timeout, String 
queryStart);
 
+  @BaseMessage("Including both WITHIN GROUP(...) and inside ORDER BY in a 
single JSON_ARRAYAGG call is not allowed")
+  ExInst<CalciteException> ambiguousSortOrderInJsonArrayAggFunc();
+
   @BaseMessage("While executing SQL [{0}] on JDBC sub-schema")
   ExInst<RuntimeException> exceptionWhilePerformingQueryOnJdbcSubSchema(String 
sql);
 
diff --git 
a/core/src/main/java/org/apache/calcite/sql/fun/SqlJsonArrayAggAggFunction.java 
b/core/src/main/java/org/apache/calcite/sql/fun/SqlJsonArrayAggAggFunction.java
index a5d1520..0569bea 100644
--- 
a/core/src/main/java/org/apache/calcite/sql/fun/SqlJsonArrayAggAggFunction.java
+++ 
b/core/src/main/java/org/apache/calcite/sql/fun/SqlJsonArrayAggAggFunction.java
@@ -22,8 +22,10 @@ import org.apache.calcite.sql.SqlCall;
 import org.apache.calcite.sql.SqlFunctionCategory;
 import org.apache.calcite.sql.SqlJsonConstructorNullClause;
 import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.SqlLiteral;
 import org.apache.calcite.sql.SqlNode;
 import org.apache.calcite.sql.SqlWriter;
+import org.apache.calcite.sql.parser.SqlParserPos;
 import org.apache.calcite.sql.type.OperandTypes;
 import org.apache.calcite.sql.type.ReturnTypes;
 import org.apache.calcite.sql.type.SqlTypeFamily;
@@ -45,7 +47,7 @@ public class SqlJsonArrayAggAggFunction extends 
SqlAggFunction {
       SqlJsonConstructorNullClause nullClause) {
     super(name, null, SqlKind.JSON_ARRAYAGG, ReturnTypes.VARCHAR_2000, null,
         OperandTypes.family(SqlTypeFamily.ANY), SqlFunctionCategory.SYSTEM,
-        false, false, Optionality.FORBIDDEN);
+        false, false, Optionality.OPTIONAL);
     this.nullClause = Objects.requireNonNull(nullClause);
   }
 
@@ -72,6 +74,25 @@ public class SqlJsonArrayAggAggFunction extends 
SqlAggFunction {
     return getName() + String.format(Locale.ROOT, "<%s>", nullClause);
   }
 
+  @Override public SqlCall createCall(SqlLiteral functionQualifier,
+      SqlParserPos pos, SqlNode... operands) {
+    assert operands.length == 1 || operands.length == 2;
+    final SqlNode valueExpr = operands[0];
+    if (operands.length == 2) {
+      final SqlNode orderList = operands[1];
+      if (orderList != null) {
+        // call has an order by clause, e.g. json_arrayagg(col_1 order by 
col_1)
+        return SqlStdOperatorTable.WITHIN_GROUP.createCall(SqlParserPos.ZERO,
+            createCall_(functionQualifier, pos, valueExpr), orderList);
+      }
+    }
+    return createCall_(functionQualifier, pos, valueExpr);
+  }
+
+  private SqlCall createCall_(SqlLiteral functionQualifier, SqlParserPos pos, 
SqlNode valueExpr) {
+    return super.createCall(functionQualifier, pos, valueExpr);
+  }
+
   public SqlJsonArrayAggAggFunction with(SqlJsonConstructorNullClause 
nullClause) {
     return this.nullClause == nullClause ? this
         : new SqlJsonArrayAggAggFunction(getName(), nullClause);
diff --git 
a/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties 
b/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
index 9202536..7c90a9b 100644
--- 
a/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
+++ 
b/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
@@ -276,6 +276,7 @@ 
ArrayOrObjectValueRequiredInStrictModeOfJsonQueryFunc=Strict jsonpath mode requi
 IllegalErrorBehaviorInJsonQueryFunc=Illegal error behavior ''{0}'' specified 
in JSON_VALUE function
 NullKeyOfJsonObjectNotAllowed=Null key of JSON object is not allowed
 QueryExecutionTimeoutReached=Timeout of ''{0}'' ms for query execution is 
reached. Query execution started at ''{1}''
+AmbiguousSortOrderInJsonArrayAggFunc=Including both WITHIN GROUP(...) and 
inside ORDER BY in a single JSON_ARRAYAGG call is not allowed
 ExceptionWhilePerformingQueryOnJdbcSubSchema = While executing SQL [{0}] on 
JDBC sub-schema
 UnknownObjectOfJsonType=Unknown JSON type in JSON_TYPE function, and the 
object is: ''{0}''
 # End CalciteResource.properties
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 83d4b8d..a4ce6c9 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
@@ -8417,7 +8417,7 @@ public class SqlParserTest {
         "JSON_ARRAY(JSON_ARRAY('foo', 'bar' ABSENT ON NULL) FORMAT JSON ABSENT 
ON NULL)");
   }
 
-  @Test public void testJsonArrayAgg() {
+  @Test public void testJsonArrayAgg1() {
     checkExp("json_arrayagg(\"column\")",
         "JSON_ARRAYAGG(`column` ABSENT ON NULL)");
     checkExp("json_arrayagg(\"column\" null on null)",
@@ -8426,6 +8426,16 @@ public class SqlParserTest {
         "JSON_ARRAYAGG(JSON_ARRAY(`column` ABSENT ON NULL) FORMAT JSON ABSENT 
ON NULL)");
   }
 
+  @Test public void testJsonArrayAgg2() {
+    checkExp("json_arrayagg(\"column\" order by \"column\")",
+        "(JSON_ARRAYAGG(`column` ABSENT ON NULL) WITHIN GROUP (ORDER BY 
`column`))");
+    checkExp("json_arrayagg(\"column\") within group (order by \"column\")",
+        "(JSON_ARRAYAGG(`column` ABSENT ON NULL) WITHIN GROUP (ORDER BY 
`column`))");
+    checkFails("^json_arrayagg(\"column\" order by \"column\") within group 
(order by \"column\")^",
+        "(?s).*Including both WITHIN GROUP\\(\\.\\.\\.\\) and inside ORDER BY "
+            + "in a single JSON_ARRAYAGG call is not allowed.*");
+  }
+
   @Test public void testJsonPredicate() {
     checkExp("'{}' is json",
         "('{}' IS JSON VALUE)");
diff --git 
a/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java 
b/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
index 5e43e1d..d3b3f73 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
@@ -2906,12 +2906,30 @@ public class SqlToRelConverterTest extends 
SqlToRelTestBase {
     sql(sql).ok();
   }
 
-  @Test public void testJsonArrayAgg() {
+  @Test public void testJsonArrayAgg1() {
     final String sql = "select json_arrayagg(ename)\n"
         + "from emp";
     sql(sql).ok();
   }
 
+  @Test public void testJsonArrayAgg2() {
+    final String sql = "select json_arrayagg(ename order by ename)\n"
+        + "from emp";
+    sql(sql).ok();
+  }
+
+  @Test public void testJsonArrayAgg3() {
+    final String sql = "select json_arrayagg(ename order by ename null on 
null)\n"
+        + "from emp";
+    sql(sql).ok();
+  }
+
+  @Test public void testJsonArrayAgg4() {
+    final String sql = "select json_arrayagg(ename null on null) within group 
(order by ename)\n"
+        + "from emp";
+    sql(sql).ok();
+  }
+
   @Test public void testJsonObject() {
     final String sql = "select json_object(ename: deptno, ename: deptno)\n"
         + "from emp";
diff --git 
a/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml 
b/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
index fbe4df8..5c670bf 100644
--- a/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
@@ -5362,7 +5362,7 @@ LogicalProject(EXPR$0=[JSON_ARRAY(FLAG(ABSENT_ON_NULL), 
JSON_STRUCTURED_VALUE_EX
 ]]>
         </Resource>
     </TestCase>
-    <TestCase name="testJsonArrayAgg">
+    <TestCase name="testJsonArrayAgg1">
         <Resource name="sql">
             <![CDATA[select json_arrayagg(ename)
 from emp]]>
@@ -5375,6 +5375,45 @@ LogicalAggregate(group=[{}], 
EXPR$0=[JSON_ARRAYAGG<ABSENT_ON_NULL>($0)])
 ]]>
         </Resource>
     </TestCase>
+    <TestCase name="testJsonArrayAgg2">
+        <Resource name="sql">
+            <![CDATA[select json_arrayagg(ename order by ename)
+from emp]]>
+        </Resource>
+        <Resource name="plan">
+            <![CDATA[
+LogicalAggregate(group=[{}], EXPR$0=[JSON_ARRAYAGG<ABSENT_ON_NULL>($0) WITHIN 
GROUP ([1])])
+  LogicalProject($f0=[JSON_STRUCTURED_VALUE_EXPRESSION($1)], ENAME=[$1])
+    LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+        </Resource>
+    </TestCase>
+    <TestCase name="testJsonArrayAgg3">
+        <Resource name="sql">
+            <![CDATA[select json_arrayagg(ename order by ename null on null)
+from emp]]>
+        </Resource>
+        <Resource name="plan">
+            <![CDATA[
+LogicalAggregate(group=[{}], EXPR$0=[JSON_ARRAYAGG<NULL_ON_NULL>($0) WITHIN 
GROUP ([1])])
+  LogicalProject($f0=[JSON_STRUCTURED_VALUE_EXPRESSION($1)], ENAME=[$1])
+    LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+        </Resource>
+    </TestCase>
+    <TestCase name="testJsonArrayAgg4">
+        <Resource name="sql">
+            <![CDATA[select json_arrayagg(ename null on null) within group 
(order by ename)
+from emp]]>
+        </Resource>
+        <Resource name="plan">
+            <![CDATA[
+LogicalAggregate(group=[{}], EXPR$0=[JSON_ARRAYAGG<NULL_ON_NULL>($0) WITHIN 
GROUP ([1])])
+  LogicalProject($f0=[JSON_STRUCTURED_VALUE_EXPRESSION($1)], ENAME=[$1])
+    LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+        </Resource>
+    </TestCase>
     <TestCase name="testJsonObject">
         <Resource name="sql">
             <![CDATA[select json_object(ename: deptno, ename: deptno)
diff --git a/core/src/test/resources/sql/agg.iq 
b/core/src/test/resources/sql/agg.iq
index 539d1c6..a1b52cd 100644
--- a/core/src/test/resources/sql/agg.iq
+++ b/core/src/test/resources/sql/agg.iq
@@ -2568,4 +2568,41 @@ select json_object('deptno': deptno, 'employees': 
json_arrayagg(json_object('ena
 
 !ok
 
+# [CALCITE-2786] Add order by clause support for JSON_ARRAYAGG
+select gender,
+json_arrayagg(deptno order by deptno),
+json_arrayagg(deptno order by deptno desc)
+from emp group by gender;
++--------+------------------+------------------+
+| GENDER | EXPR$1           | EXPR$2           |
++--------+------------------+------------------+
+| F      | [10,30,30,50,60] | [60,50,30,30,10] |
+| M      | [10,20,50]       | [50,20,10]       |
++--------+------------------+------------------+
+(2 rows)
+
+!ok
+EnumerableAggregate(group=[{0}], EXPR$1=[JSON_ARRAYAGG<ABSENT_ON_NULL>($1) 
WITHIN GROUP ([2])], EXPR$2=[JSON_ARRAYAGG<ABSENT_ON_NULL>($1) WITHIN GROUP ([2 
DESC])])
+  EnumerableCalc(expr#0..1=[{inputs}], 
expr#2=[JSON_STRUCTURED_VALUE_EXPRESSION($t0)], GENDER=[$t1], $f1=[$t2], 
DEPTNO=[$t0])
+    EnumerableUnion(all=[true])
+      EnumerableCalc(expr#0=[{inputs}], expr#1=[10], expr#2=['F'], 
EXPR$1=[$t1], EXPR$2=[$t2])
+        EnumerableValues(tuples=[[{ 0 }]])
+      EnumerableCalc(expr#0=[{inputs}], expr#1=[10], expr#2=['M'], 
EXPR$1=[$t1], EXPR$2=[$t2])
+        EnumerableValues(tuples=[[{ 0 }]])
+      EnumerableCalc(expr#0=[{inputs}], expr#1=[20], expr#2=['M'], 
EXPR$1=[$t1], EXPR$2=[$t2])
+        EnumerableValues(tuples=[[{ 0 }]])
+      EnumerableCalc(expr#0=[{inputs}], expr#1=[30], expr#2=['F'], 
EXPR$1=[$t1], EXPR$2=[$t2])
+        EnumerableValues(tuples=[[{ 0 }]])
+      EnumerableCalc(expr#0=[{inputs}], expr#1=[30], expr#2=['F'], 
EXPR$1=[$t1], EXPR$2=[$t2])
+        EnumerableValues(tuples=[[{ 0 }]])
+      EnumerableCalc(expr#0=[{inputs}], expr#1=[50], expr#2=['M'], 
EXPR$1=[$t1], EXPR$2=[$t2])
+        EnumerableValues(tuples=[[{ 0 }]])
+      EnumerableCalc(expr#0=[{inputs}], expr#1=[50], expr#2=['F'], 
EXPR$1=[$t1], EXPR$2=[$t2])
+        EnumerableValues(tuples=[[{ 0 }]])
+      EnumerableCalc(expr#0=[{inputs}], expr#1=[60], expr#2=['F'], 
EXPR$1=[$t1], EXPR$2=[$t2])
+        EnumerableValues(tuples=[[{ 0 }]])
+      EnumerableCalc(expr#0=[{inputs}], expr#1=[null:INTEGER], expr#2=['F'], 
EXPR$1=[$t1], EXPR$2=[$t2])
+        EnumerableValues(tuples=[[{ 0 }]])
+!plan
+
 # End agg.iq
diff --git a/site/_docs/reference.md b/site/_docs/reference.md
index d429ac3..2ade413 100644
--- a/site/_docs/reference.md
+++ b/site/_docs/reference.md
@@ -1975,12 +1975,13 @@ Not implemented:
 | JSON_OBJECT( { [ KEY ] name VALUE value [ FORMAT JSON ] &#124; name : value 
[ FORMAT JSON ] } * [ { NULL &#124; ABSENT } ON NULL ] ) | Construct json 
object using a series of key (**name**) value (**value**) pairs
 | JSON_OBJECTAGG( { [ KEY ] name VALUE value [ FORMAT JSON ] &#124; name : 
value [ FORMAT JSON ] } [ { NULL &#124; ABSENT } ON NULL ] ) | Aggregate 
function to construct json object using a key (**name**) value (**value**) pair
 | JSON_ARRAY( { value [ FORMAT JSON ] } * [ { NULL &#124; ABSENT } ON NULL ] ) 
| Construct json array using a series of values (**value**)
-| JSON_ARRAYAGG( value [ FORMAT JSON ] [ { NULL &#124; ABSENT } ON NULL ] ) | 
Aggregate function to construct json array using a value (**value**)
+| JSON_ARRAYAGG( value [ FORMAT JSON ] [ ORDER BY orderItem [, orderItem ]* ] 
[ { NULL &#124; ABSENT } ON NULL ] ) | Aggregate function to construct json 
array using a value (**value**)
 
 Note:
 
 * The flag **FORMAT JSON** indicates the value is formatted as JSON character 
string. When **FORMAT JSON** is used, value should be de-parse from JSON 
character string to SQL structured value.
 * **ON NULL** clause defines how the JSON output represents null value. The 
default null behavior of **JSON_OBJECT** and **JSON_OBJECTAGG** is *NULL ON 
NULL*, and for **JSON_ARRAY** and **JSON_ARRAYAGG** it is *ABSENT ON NULL*.
+* If **ORDER BY** clause is provided, **JSON_ARRAYAGG** will sort the input 
rows by the specified order before performing aggregation.
 
 #### Comparison Operators
 

Reply via email to