Skip to content

Commit 8696b0a

Browse files
committed
[feature](analyzer) support * as json_object's parameter
1 parent e1884e4 commit 8696b0a

File tree

6 files changed

+201
-24
lines changed

6 files changed

+201
-24
lines changed

fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3181,34 +3181,38 @@ private Expression processUnboundFunction(ParserRuleContext ctx, String dbName,
31813181
WindowSpecContext windowContext, IdentifierContext hintContext) {
31823182
List<UnboundStar> unboundStars = ExpressionUtils.collectAll(params, UnboundStar.class::isInstance);
31833183
if (!unboundStars.isEmpty()) {
3184-
if (dbName == null && functionName.equalsIgnoreCase("count")) {
3184+
if (dbName != null
3185+
|| (!functionName.equalsIgnoreCase("count")
3186+
&& !functionName.equalsIgnoreCase("json_object"))) {
3187+
throw new ParseException("'*' can only be used in conjunction with"
3188+
+ " COUNT or JSON_OBJECT: " + functionName, ctx);
3189+
}
3190+
if (functionName.equalsIgnoreCase("count")) {
31853191
if (unboundStars.size() > 1) {
31863192
throw new ParseException(
31873193
"'*' can only be used once in conjunction with COUNT: " + functionName, ctx);
31883194
}
31893195
if (!unboundStars.get(0).getQualifier().isEmpty()) {
3190-
throw new ParseException("'*' can not has qualifier: " + unboundStars.size(), ctx);
3196+
throw new ParseException("'*' can not has qualifier with COUNT: " + unboundStars.size(), ctx);
31913197
}
31923198
if (windowContext != null) {
31933199
return withWindowSpec(windowContext, new Count());
31943200
}
31953201
return new Count();
31963202
}
3197-
throw new ParseException("'*' can only be used in conjunction with COUNT: " + functionName, ctx);
3198-
} else {
3199-
boolean isSkew = hintContext != null && hintContext.getText().equalsIgnoreCase("skew");
3200-
UnboundFunction function = new UnboundFunction(dbName, functionName, isDistinct, params, isSkew);
3201-
if (windowContext != null) {
3202-
if (isDistinct
3203-
&& !("count".equalsIgnoreCase(functionName))
3204-
&& !("sum".equalsIgnoreCase(functionName))
3205-
&& !("group_concat".equalsIgnoreCase(functionName))) {
3206-
throw new ParseException("DISTINCT not allowed in analytic function: " + functionName, ctx);
3207-
}
3208-
return withWindowSpec(windowContext, function);
3203+
}
3204+
boolean isSkew = hintContext != null && hintContext.getText().equalsIgnoreCase("skew");
3205+
UnboundFunction function = new UnboundFunction(dbName, functionName, isDistinct, params, isSkew);
3206+
if (windowContext != null) {
3207+
if (isDistinct
3208+
&& !("count".equalsIgnoreCase(functionName))
3209+
&& !("sum".equalsIgnoreCase(functionName))
3210+
&& !("group_concat".equalsIgnoreCase(functionName))) {
3211+
throw new ParseException("DISTINCT not allowed in analytic function: " + functionName, ctx);
32093212
}
3210-
return function;
3213+
return withWindowSpec(windowContext, function);
32113214
}
3215+
return function;
32123216
}
32133217

32143218
/**

fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/ExpressionAnalyzer.java

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -433,19 +433,33 @@ UnboundFunction preProcessUnboundFunction(UnboundFunction unboundFunction, Expre
433433
return unboundFunction;
434434
}
435435

436+
List<Object> constructUnboundFunctionArguments(UnboundFunction unboundFunction) {
437+
ImmutableList.Builder<Object> argumentsBuilder
438+
= ImmutableList.builderWithExpectedSize(unboundFunction.arity() + 1);
439+
if (unboundFunction.isDistinct()) {
440+
argumentsBuilder.add(unboundFunction.isDistinct());
441+
}
442+
for (Expression argument : unboundFunction.getArguments()) {
443+
if (argument instanceof BoundStar) {
444+
BoundStar boundStar = (BoundStar) argument;
445+
for (Slot slot : boundStar.getSlots()) {
446+
argumentsBuilder.add(new StringLiteral(slot.getName()));
447+
argumentsBuilder.add(slot);
448+
}
449+
} else {
450+
argumentsBuilder.add(argument);
451+
}
452+
}
453+
return argumentsBuilder.build();
454+
}
455+
436456
@Override
437457
public Expression visitUnboundFunction(UnboundFunction unboundFunction, ExpressionRewriteContext context) {
438458
unboundFunction = preProcessUnboundFunction(unboundFunction, context);
439459

440460
// bind function
441461
FunctionRegistry functionRegistry = Env.getCurrentEnv().getFunctionRegistry();
442-
List<Object> arguments = unboundFunction.isDistinct()
443-
? ImmutableList.builderWithExpectedSize(unboundFunction.arity() + 1)
444-
.add(unboundFunction.isDistinct())
445-
.addAll(unboundFunction.getArguments())
446-
.build()
447-
: (List) unboundFunction.getArguments();
448-
462+
List<Object> arguments = constructUnboundFunctionArguments(unboundFunction);
449463
String dbName = unboundFunction.getDbName();
450464
if (StringUtils.isEmpty(dbName)) {
451465
// we will change arithmetic function like add(), subtract(), bitnot()
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package org.apache.doris.nereids.parser;
19+
20+
import org.apache.doris.nereids.analyzer.UnboundFunction;
21+
import org.apache.doris.nereids.analyzer.UnboundStar;
22+
import org.apache.doris.nereids.exceptions.ParseException;
23+
import org.apache.doris.nereids.trees.expressions.Expression;
24+
import org.apache.doris.nereids.trees.expressions.functions.agg.Count;
25+
26+
import com.google.common.collect.ImmutableList;
27+
import org.junit.jupiter.api.Assertions;
28+
import org.junit.jupiter.api.Test;
29+
30+
public class UnboundFunctionWithUnboundStarTest {
31+
32+
@Test
33+
public void testCount() {
34+
NereidsParser parser = new NereidsParser();
35+
Expression result = parser.parseExpression("COUNT(*)");
36+
Assertions.assertEquals(new Count(), result);
37+
}
38+
39+
@Test
40+
public void testJsonObject() {
41+
NereidsParser parser = new NereidsParser();
42+
Expression result = parser.parseExpression("JSON_OBJECT(*)");
43+
Assertions.assertInstanceOf(UnboundFunction.class, result);
44+
UnboundFunction unboundFunction = (UnboundFunction) result;
45+
Assertions.assertEquals("JSON_OBJECT", unboundFunction.getName());
46+
Assertions.assertEquals(1, unboundFunction.arity());
47+
Assertions.assertEquals(new UnboundStar(ImmutableList.of()), unboundFunction.child(0));
48+
}
49+
50+
@Test
51+
public void testOtherFunctionName() {
52+
NereidsParser parser = new NereidsParser();
53+
Exception exception = Assertions.assertThrowsExactly(ParseException.class,
54+
() -> parser.parseExpression("OTHER(*)"));
55+
Assertions.assertTrue(exception.getMessage()
56+
.contains("'*' can only be used in conjunction with COUNT or JSON_OBJECT: "),
57+
exception.getMessage());
58+
}
59+
60+
@Test
61+
public void testFunctionNameWithDbName() {
62+
NereidsParser parser = new NereidsParser();
63+
Exception exception = Assertions.assertThrowsExactly(ParseException.class,
64+
() -> parser.parseExpression("db.COUNT(*)"));
65+
Assertions.assertTrue(exception.getMessage()
66+
.contains("'*' can only be used in conjunction with COUNT or JSON_OBJECT: "),
67+
exception.getMessage());
68+
}
69+
70+
@Test
71+
public void testStarWithQualifier() {
72+
NereidsParser parser = new NereidsParser();
73+
Exception exception = Assertions.assertThrowsExactly(ParseException.class,
74+
() -> parser.parseExpression("COUNT(t1.*)"));
75+
Assertions.assertTrue(exception.getMessage()
76+
.contains("'*' can not has qualifier with COUNT: "),
77+
exception.getMessage());
78+
79+
Expression result = parser.parseExpression("JSON_OBJECT(t1.*)");
80+
Assertions.assertInstanceOf(UnboundFunction.class, result);
81+
UnboundFunction unboundFunction = (UnboundFunction) result;
82+
Assertions.assertEquals("JSON_OBJECT", unboundFunction.getName());
83+
Assertions.assertEquals(1, unboundFunction.arity());
84+
Assertions.assertEquals(new UnboundStar(ImmutableList.of("t1")), unboundFunction.child(0));
85+
}
86+
87+
@Test
88+
public void testMoreThanOneStar() {
89+
NereidsParser parser = new NereidsParser();
90+
Exception exception = Assertions.assertThrowsExactly(ParseException.class,
91+
() -> parser.parseExpression("COUNT(*, *)"));
92+
Assertions.assertTrue(exception.getMessage()
93+
.contains("'*' can only be used once in conjunction with COUNT: "),
94+
exception.getMessage());
95+
96+
Expression result = parser.parseExpression("JSON_OBJECT(t2.*, t1.*, *)");
97+
Assertions.assertInstanceOf(UnboundFunction.class, result);
98+
UnboundFunction unboundFunction = (UnboundFunction) result;
99+
Assertions.assertEquals("JSON_OBJECT", unboundFunction.getName());
100+
Assertions.assertEquals(3, unboundFunction.arity());
101+
Assertions.assertEquals(new UnboundStar(ImmutableList.of("t2")), unboundFunction.child(0));
102+
Assertions.assertEquals(new UnboundStar(ImmutableList.of("t1")), unboundFunction.child(1));
103+
Assertions.assertEquals(new UnboundStar(ImmutableList.of()), unboundFunction.child(2));
104+
}
105+
106+
}

fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/ExpressionAnalyzerTest.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,21 @@
2121
import org.apache.doris.nereids.analyzer.UnboundFunction;
2222
import org.apache.doris.nereids.analyzer.UnboundSlot;
2323
import org.apache.doris.nereids.exceptions.AnalysisException;
24+
import org.apache.doris.nereids.trees.expressions.BoundStar;
25+
import org.apache.doris.nereids.trees.expressions.ExprId;
2426
import org.apache.doris.nereids.trees.expressions.Expression;
2527
import org.apache.doris.nereids.trees.expressions.SlotReference;
2628
import org.apache.doris.nereids.trees.expressions.literal.DateTimeV2Literal;
29+
import org.apache.doris.nereids.trees.expressions.literal.StringLiteral;
2730
import org.apache.doris.nereids.trees.expressions.literal.TinyIntLiteral;
31+
import org.apache.doris.nereids.types.BigIntType;
2832

2933
import com.google.common.collect.ImmutableList;
3034
import org.junit.jupiter.api.Assertions;
3135
import org.junit.jupiter.api.Test;
3236

37+
import java.util.List;
38+
3339
public class ExpressionAnalyzerTest {
3440

3541
@Test
@@ -60,4 +66,34 @@ void testPreProcessUnboundFunctionForThreeArgsDataTimeFunction() {
6066
() -> analyzer.preProcessUnboundFunction(unboundFunction, null),
6167
" Unknown column 'YEAR' in 'table list");
6268
}
69+
70+
@Test
71+
public void testConstructUnboundFunctionArguments() {
72+
ExpressionAnalyzer analyzer = new ExpressionAnalyzer(null, new Scope(ImmutableList.of()),
73+
null, true, true);
74+
BoundStar boundStar1 = new BoundStar(ImmutableList.of(
75+
new SlotReference(new ExprId(1), "c1", BigIntType.INSTANCE, true, ImmutableList.of()),
76+
new SlotReference(new ExprId(2), "c2", BigIntType.INSTANCE, true, ImmutableList.of())
77+
));
78+
BoundStar boundStar2 = new BoundStar(ImmutableList.of(
79+
new SlotReference(new ExprId(3), "c3", BigIntType.INSTANCE, true, ImmutableList.of()),
80+
new SlotReference(new ExprId(4), "c4", BigIntType.INSTANCE, true, ImmutableList.of())
81+
));
82+
SlotReference slotReference = new SlotReference(new ExprId(5), "c5", BigIntType.INSTANCE, true, ImmutableList.of());
83+
UnboundFunction unboundFunction = new UnboundFunction("json_object", true,
84+
ImmutableList.of(boundStar2, slotReference, boundStar1));
85+
List<Object> result = analyzer.constructUnboundFunctionArguments(unboundFunction);
86+
List<Object> expectedResult = ImmutableList.of(true,
87+
new StringLiteral("c3"),
88+
new SlotReference(new ExprId(3), "c3", BigIntType.INSTANCE, true, ImmutableList.of()),
89+
new StringLiteral("c4"),
90+
new SlotReference(new ExprId(4), "c4", BigIntType.INSTANCE, true, ImmutableList.of()),
91+
new SlotReference(new ExprId(5), "c5", BigIntType.INSTANCE, true, ImmutableList.of()),
92+
new StringLiteral("c1"),
93+
new SlotReference(new ExprId(1), "c1", BigIntType.INSTANCE, true, ImmutableList.of()),
94+
new StringLiteral("c2"),
95+
new SlotReference(new ExprId(2), "c2", BigIntType.INSTANCE, true, ImmutableList.of())
96+
);
97+
Assertions.assertEquals(expectedResult, result);
98+
}
6399
}
Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,22 @@
11
-- This file is automatically generated. You should know what you did if you want to edit this
2-
-- !sql --
2+
-- !test_normal --
33
{"k0":1,"k1":null,"k2":null,"k3":null,"k4":null,"k5":null,"k6":"k6"}
44
{"k0":2,"k1":1,"k2":null,"k3":null,"k4":null,"k5":null,"k6":"k6"}
55
{"k0":3,"k1":null,"k2":true,"k3":null,"k4":null,"k5":null,"k6":"k6"}
66
{"k0":4,"k1":null,"k2":null,"k3":"test","k4":"2022-01-01 11:11:11","k5":null,"k6":"k6"}
77
{"k0":5,"k1":1,"k2":true,"k3":"test","k4":"2022-01-01 11:11:11","k5":null,"k6":"k6"}
88

9+
-- !test_star --
10+
{"k0":1,"k1":null,"k2":null,"k3":null,"k4":null}
11+
{"k0":2,"k1":1,"k2":null,"k3":null,"k4":null}
12+
{"k0":3,"k1":null,"k2":true,"k3":null,"k4":null}
13+
{"k0":4,"k1":null,"k2":null,"k3":"test","k4":"2022-01-01 11:11:11"}
14+
{"k0":5,"k1":1,"k2":true,"k3":"test","k4":"2022-01-01 11:11:11"}
15+
16+
-- !test_star_with_qualifer --
17+
{"k0":1,"k1":null,"k2":null,"k3":null,"k4":null}
18+
{"k0":2,"k1":1,"k2":null,"k3":null,"k4":null}
19+
{"k0":3,"k1":null,"k2":true,"k3":null,"k4":null}
20+
{"k0":4,"k1":null,"k2":null,"k3":"test","k4":"2022-01-01 11:11:11"}
21+
{"k0":5,"k1":1,"k2":true,"k3":"test","k4":"2022-01-01 11:11:11"}
22+

regression-test/suites/nereids_p0/sql_functions/json_function/test_query_json_object.groovy

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ suite("test_query_json_object", "query") {
4242
sql "insert into ${tableName} values(3,null,true,null,null);"
4343
sql "insert into ${tableName} values(4,null,null,'test','2022-01-01 11:11:11');"
4444
sql "insert into ${tableName} values(5,1,true,'test','2022-01-01 11:11:11');"
45-
order_qt_sql "select json_object('k0',k0,'k1',k1,'k2',k2,'k3',k3,'k4',cast(k4 as string),'k5', null,'k6','k6') from ${tableName};"
45+
sql "sync"
46+
order_qt_test_normal "select json_object('k0',k0,'k1',k1,'k2',k2,'k3',k3,'k4',cast(k4 as string),'k5', null,'k6','k6') from ${tableName};"
47+
order_qt_test_star "select json_object(*) from ${tableName};"
48+
order_qt_test_star_with_qualifer "select json_object(${tableName}.*) from ${tableName};"
4649
sql "DROP TABLE ${tableName};"
4750
}

0 commit comments

Comments
 (0)