This is an automated email from the ASF dual-hosted git repository.
cwylie pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/druid.git
The following commit(s) were added to refs/heads/master by this push:
new e64b92eb351 add JSON_QUERY_ARRAY function to pluck
ARRAY<COMPLEX<json>> out of COMPLEX<json> (#15521)
e64b92eb351 is described below
commit e64b92eb35119a0cd404d6a4e93054ce0df2b234
Author: Clint Wylie <[email protected]>
AuthorDate: Fri Dec 8 05:28:46 2023 -0800
add JSON_QUERY_ARRAY function to pluck ARRAY<COMPLEX<json>> out of
COMPLEX<json> (#15521)
---
docs/querying/math-expr.md | 1 +
docs/querying/sql-json-functions.md | 1 +
.../org/apache/druid/guice/ExpressionModule.java | 1 +
.../query/expression/NestedDataExpressions.java | 117 +++++++++++
.../druid/segment/column/ColumnTypeFactory.java | 2 +-
.../segment/virtual/NestedFieldVirtualColumn.java | 7 +
.../expression/NestedDataExpressionsTest.java | 34 ++++
.../builtin/NestedDataOperatorConversions.java | 32 +++
.../sql/calcite/planner/DruidOperatorTable.java | 1 +
.../sql/calcite/CalciteNestedDataQueryTest.java | 214 +++++++++++++++++++++
website/.spelling | 1 +
11 files changed, 410 insertions(+), 1 deletion(-)
diff --git a/docs/querying/math-expr.md b/docs/querying/math-expr.md
index 2550e30da96..01f9fe95d32 100644
--- a/docs/querying/math-expr.md
+++ b/docs/querying/math-expr.md
@@ -237,6 +237,7 @@ JSON functions provide facilities to extract, transform,
and create `COMPLEX<jso
|---|---|
| json_value(expr, path[, type]) | Extract a Druid literal (`STRING`, `LONG`,
`DOUBLE`, `ARRAY<STRING>`, `ARRAY<LONG>`, or `ARRAY<DOUBLE>`) value from `expr`
using JSONPath syntax of `path`. The optional `type` argument can be set to
`'LONG'`,`'DOUBLE'`, `'STRING'`, `'ARRAY<LONG>'`, `'ARRAY<DOUBLE>'`, or
`'ARRAY<STRING>'` to cast values to that type. |
| json_query(expr, path) | Extract a `COMPLEX<json>` value from `expr` using
JSONPath syntax of `path` |
+| json_query_array(expr, path) | Extract an `ARRAY<COMPLEX<json>>` value from
`expr` using JSONPath syntax of `path`. If value is not an `ARRAY`, it will be
translated into a single element `ARRAY` containing the value at `path`. |
| json_object(expr1, expr2[, expr3, expr4 ...]) | Construct a `COMPLEX<json>`
with alternating 'key' and 'value' arguments|
| parse_json(expr) | Deserialize a JSON `STRING` into a `COMPLEX<json>`. If
the input is not a `STRING` or it is invalid JSON, this function will result in
an error.|
| try_parse_json(expr) | Deserialize a JSON `STRING` into a `COMPLEX<json>`.
If the input is not a `STRING` or it is invalid JSON, this function will result
in a `NULL` value. |
diff --git a/docs/querying/sql-json-functions.md
b/docs/querying/sql-json-functions.md
index 1f3c681392e..d499fef5087 100644
--- a/docs/querying/sql-json-functions.md
+++ b/docs/querying/sql-json-functions.md
@@ -40,6 +40,7 @@ You can use the following JSON functions to extract,
transform, and create `COMP
|`JSON_OBJECT(KEY expr1 VALUE expr2[, KEY expr3 VALUE expr4, ...])` |
Constructs a new `COMPLEX<json>` object. The `KEY` expressions must evaluate to
string types. The `VALUE` expressions can be composed of any input type,
including other `COMPLEX<json>` values. `JSON_OBJECT` can accept
colon-separated key-value pairs. The following syntax is equivalent:
`JSON_OBJECT(expr1:expr2[, expr3:expr4, ...])`.|
|`JSON_PATHS(expr)`| Returns an array of all paths which refer to literal
values in `expr` in JSONPath format. |
|`JSON_QUERY(expr, path)`| Extracts a `COMPLEX<json>` value from `expr`, at
the specified `path`. |
+|`JSON_QUERY_ARRAY(expr, path)`| Extracts an `ARRAY<COMPLEX<json>>` value from
`expr`, at the specified `path`. If value is not an `ARRAY`, it will be
translated into a single element `ARRAY` containing the value at `path`.|
|`JSON_VALUE(expr, path [RETURNING sqlType])`| Extracts a literal value from
`expr` at the specified `path`. If you specify `RETURNING` and an SQL type name
(such as `VARCHAR`, `BIGINT`, `DOUBLE`, etc) the function plans the query using
the suggested type. Otherwise, it attempts to infer the type based on the
context. If it can't infer the type, it defaults to `VARCHAR`.|
|`PARSE_JSON(expr)`|Parses `expr` into a `COMPLEX<json>` object. This operator
deserializes JSON values when processing them, translating stringified JSON
into a nested structure. If the input is not a `VARCHAR` or it is invalid JSON,
this function will result in an error.|
|`TRY_PARSE_JSON(expr)`|Parses `expr` into a `COMPLEX<json>` object. This
operator deserializes JSON values when processing them, translating stringified
JSON into a nested structure. If the input is not a `VARCHAR` or it is invalid
JSON, this function will result in a `NULL` value.|
diff --git
a/processing/src/main/java/org/apache/druid/guice/ExpressionModule.java
b/processing/src/main/java/org/apache/druid/guice/ExpressionModule.java
index 7d740721670..917cf967f14 100644
--- a/processing/src/main/java/org/apache/druid/guice/ExpressionModule.java
+++ b/processing/src/main/java/org/apache/druid/guice/ExpressionModule.java
@@ -85,6 +85,7 @@ public class ExpressionModule implements Module
.add(NestedDataExpressions.JsonPathsExprMacro.class)
.add(NestedDataExpressions.JsonValueExprMacro.class)
.add(NestedDataExpressions.JsonQueryExprMacro.class)
+ .add(NestedDataExpressions.JsonQueryArrayExprMacro.class)
.add(NestedDataExpressions.ToJsonStringExprMacro.class)
.add(NestedDataExpressions.ParseJsonExprMacro.class)
.add(NestedDataExpressions.TryParseJsonExprMacro.class)
diff --git
a/processing/src/main/java/org/apache/druid/query/expression/NestedDataExpressions.java
b/processing/src/main/java/org/apache/druid/query/expression/NestedDataExpressions.java
index fec16ad99b1..f7476adf599 100644
---
a/processing/src/main/java/org/apache/druid/query/expression/NestedDataExpressions.java
+++
b/processing/src/main/java/org/apache/druid/query/expression/NestedDataExpressions.java
@@ -28,6 +28,7 @@ import org.apache.druid.math.expr.ExprEval;
import org.apache.druid.math.expr.ExprMacroTable;
import org.apache.druid.math.expr.ExprType;
import org.apache.druid.math.expr.ExpressionType;
+import org.apache.druid.math.expr.ExpressionTypeFactory;
import org.apache.druid.math.expr.NamedFunction;
import org.apache.druid.segment.nested.NestedPathFinder;
import org.apache.druid.segment.nested.NestedPathPart;
@@ -44,6 +45,8 @@ import java.util.stream.Collectors;
public class NestedDataExpressions
{
+ private static ExpressionType JSON_ARRAY =
ExpressionTypeFactory.getInstance().ofArray(ExpressionType.NESTED_DATA);
+
public static class JsonObjectExprMacro implements ExprMacroTable.ExprMacro
{
public static final String NAME = "json_object";
@@ -591,6 +594,120 @@ public class NestedDataExpressions
}
}
+ public static class JsonQueryArrayExprMacro implements
ExprMacroTable.ExprMacro
+ {
+ public static final String NAME = "json_query_array";
+
+ @Override
+ public String name()
+ {
+ return NAME;
+ }
+
+ @Override
+ public Expr apply(List<Expr> args)
+ {
+ if (args.get(1).isLiteral()) {
+ return new JsonQueryArrayExpr(args);
+ } else {
+ return new JsonQueryArrayDynamicExpr(args);
+ }
+ }
+
+ final class JsonQueryArrayExpr extends
ExprMacroTable.BaseScalarMacroFunctionExpr
+ {
+ private final List<NestedPathPart> parts;
+
+ public JsonQueryArrayExpr(List<Expr> args)
+ {
+ super(name(), args);
+ this.parts = getJsonPathPartsFromLiteral(JsonQueryArrayExprMacro.this,
args.get(1));
+ }
+
+ @Override
+ public ExprEval eval(ObjectBinding bindings)
+ {
+ ExprEval input = args.get(0).eval(bindings);
+ final Object value = NestedPathFinder.find(unwrap(input), parts);
+ if (value instanceof List) {
+ return ExprEval.ofArray(
+ JSON_ARRAY,
+ ExprEval.bestEffortArray((List) value).asArray()
+ );
+ }
+ return ExprEval.ofArray(
+ JSON_ARRAY,
+ ExprEval.bestEffortOf(value).asArray()
+ );
+ }
+
+ @Override
+ public Expr visit(Shuttle shuttle)
+ {
+ List<Expr> newArgs = args.stream().map(x ->
x.visit(shuttle)).collect(Collectors.toList());
+ if (newArgs.get(1).isLiteral()) {
+ return shuttle.visit(new JsonQueryArrayExpr(newArgs));
+ } else {
+ return shuttle.visit(new JsonQueryArrayDynamicExpr(newArgs));
+ }
+ }
+
+ @Nullable
+ @Override
+ public ExpressionType getOutputType(InputBindingInspector inspector)
+ {
+ // call all the output JSON typed
+ return ExpressionType.NESTED_DATA;
+ }
+ }
+
+ final class JsonQueryArrayDynamicExpr extends
ExprMacroTable.BaseScalarMacroFunctionExpr
+ {
+ public JsonQueryArrayDynamicExpr(List<Expr> args)
+ {
+ super(name(), args);
+ }
+
+ @Override
+ public ExprEval eval(ObjectBinding bindings)
+ {
+ ExprEval input = args.get(0).eval(bindings);
+ ExprEval path = args.get(1).eval(bindings);
+ final List<NestedPathPart> parts =
NestedPathFinder.parseJsonPath(path.asString());
+ final Object value = NestedPathFinder.find(unwrap(input), parts);
+ if (value instanceof List) {
+ return ExprEval.ofArray(
+ JSON_ARRAY,
+ ExprEval.bestEffortArray((List) value).asArray()
+ );
+ }
+ return ExprEval.ofArray(
+ JSON_ARRAY,
+ ExprEval.bestEffortOf(value).asArray()
+ );
+ }
+
+ @Override
+ public Expr visit(Shuttle shuttle)
+ {
+ List<Expr> newArgs = args.stream().map(x ->
x.visit(shuttle)).collect(Collectors.toList());
+ if (newArgs.get(1).isLiteral()) {
+ return shuttle.visit(new JsonQueryArrayExpr(newArgs));
+ } else {
+ return shuttle.visit(new JsonQueryArrayDynamicExpr(newArgs));
+ }
+ }
+
+ @Nullable
+ @Override
+ public ExpressionType getOutputType(InputBindingInspector inspector)
+ {
+ // call all the output ARRAY<COMPLEX<json>> typed
+ return JSON_ARRAY;
+ }
+ }
+ }
+
public static class JsonPathsExprMacro implements ExprMacroTable.ExprMacro
{
public static final String NAME = "json_paths";
diff --git
a/processing/src/main/java/org/apache/druid/segment/column/ColumnTypeFactory.java
b/processing/src/main/java/org/apache/druid/segment/column/ColumnTypeFactory.java
index 49793fba416..c9b153bf121 100644
---
a/processing/src/main/java/org/apache/druid/segment/column/ColumnTypeFactory.java
+++
b/processing/src/main/java/org/apache/druid/segment/column/ColumnTypeFactory.java
@@ -64,7 +64,7 @@ public class ColumnTypeFactory implements
TypeFactory<ColumnType>
case STRING:
return ColumnType.STRING_ARRAY;
default:
- throw new ISE("Unsupported expression type[%s]",
type.asTypeString());
+ return ColumnType.ofArray(ofType(type.getElementType()));
}
case COMPLEX:
return INTERNER.intern(new ColumnType(ValueType.COMPLEX,
type.getComplexTypeName(), null));
diff --git
a/processing/src/main/java/org/apache/druid/segment/virtual/NestedFieldVirtualColumn.java
b/processing/src/main/java/org/apache/druid/segment/virtual/NestedFieldVirtualColumn.java
index e486e7b7738..0a282f5e4bc 100644
---
a/processing/src/main/java/org/apache/druid/segment/virtual/NestedFieldVirtualColumn.java
+++
b/processing/src/main/java/org/apache/druid/segment/virtual/NestedFieldVirtualColumn.java
@@ -1228,6 +1228,13 @@ public class NestedFieldVirtualColumn implements
VirtualColumn
public ColumnCapabilities capabilities(ColumnInspector inspector, String
columnName)
{
if (processFromRaw) {
+ if (expectedType != null && expectedType.isArray() &&
ColumnType.NESTED_DATA.equals(expectedType.getElementType())) {
+ // arrays of objects!
+ return ColumnCapabilitiesImpl.createDefault()
+
.setType(ColumnType.ofArray(ColumnType.NESTED_DATA))
+ .setHasMultipleValues(false)
+ .setHasNulls(true);
+ }
// JSON_QUERY always returns a StructuredData
return ColumnCapabilitiesImpl.createDefault()
.setType(ColumnType.NESTED_DATA)
diff --git
a/processing/src/test/java/org/apache/druid/query/expression/NestedDataExpressionsTest.java
b/processing/src/test/java/org/apache/druid/query/expression/NestedDataExpressionsTest.java
index d8bd2d93841..b14edb2d17b 100644
---
a/processing/src/test/java/org/apache/druid/query/expression/NestedDataExpressionsTest.java
+++
b/processing/src/test/java/org/apache/druid/query/expression/NestedDataExpressionsTest.java
@@ -29,6 +29,7 @@ import org.apache.druid.math.expr.ExprEval;
import org.apache.druid.math.expr.ExprMacroTable;
import org.apache.druid.math.expr.ExpressionProcessingException;
import org.apache.druid.math.expr.ExpressionType;
+import org.apache.druid.math.expr.ExpressionTypeFactory;
import org.apache.druid.math.expr.InputBindings;
import org.apache.druid.math.expr.Parser;
import org.apache.druid.segment.nested.StructuredData;
@@ -37,6 +38,7 @@ import org.junit.Assert;
import org.junit.Test;
import java.util.Arrays;
+import java.util.List;
import java.util.Map;
public class NestedDataExpressionsTest extends InitializedNullHandlingTest
@@ -49,6 +51,7 @@ public class NestedDataExpressionsTest extends
InitializedNullHandlingTest
new NestedDataExpressions.JsonObjectExprMacro(),
new NestedDataExpressions.JsonValueExprMacro(),
new NestedDataExpressions.JsonQueryExprMacro(),
+ new NestedDataExpressions.JsonQueryArrayExprMacro(),
new NestedDataExpressions.ToJsonStringExprMacro(JSON_MAPPER),
new NestedDataExpressions.ParseJsonExprMacro(JSON_MAPPER),
new NestedDataExpressions.TryParseJsonExprMacro(JSON_MAPPER)
@@ -329,6 +332,37 @@ public class NestedDataExpressionsTest extends
InitializedNullHandlingTest
Assert.assertEquals(ExpressionType.NESTED_DATA, eval.type());
}
+ @Test
+ public void testJsonQueryArrayExpression()
+ {
+ final ExpressionType nestedArray =
ExpressionTypeFactory.getInstance().ofArray(ExpressionType.NESTED_DATA);
+
+ Expr expr = Parser.parse("json_query_array(nest, '$.x')", MACRO_TABLE);
+ ExprEval eval = expr.eval(inputBindings);
+ Assert.assertArrayEquals(new Object[]{100L}, (Object[]) eval.value());
+ Assert.assertEquals(nestedArray, eval.type());
+
+ expr = Parser.parse("json_query_array(nester, '$.x')", MACRO_TABLE);
+ eval = expr.eval(inputBindings);
+ Assert.assertArrayEquals(((List) NESTER.get("x")).toArray(), (Object[])
eval.value());
+ Assert.assertEquals(nestedArray, eval.type());
+
+ expr = Parser.parse("json_query_array(nester,
array_offset(json_paths(nester), 0))", MACRO_TABLE);
+ eval = expr.eval(inputBindings);
+ Assert.assertArrayEquals(((List) NESTER.get("x")).toArray(), (Object[])
eval.value());
+ Assert.assertEquals(nestedArray, eval.type());
+
+ expr = Parser.parse("json_query_array(nesterer, '$.y')", MACRO_TABLE);
+ eval = expr.eval(inputBindings);
+ Assert.assertArrayEquals(((List) NESTERER.get("y")).toArray(), (Object[])
eval.value());
+ Assert.assertEquals(nestedArray, eval.type());
+
+ expr = Parser.parse("array_length(json_query_array(nesterer, '$.y'))",
MACRO_TABLE);
+ eval = expr.eval(inputBindings);
+ Assert.assertEquals(3L, eval.value());
+ Assert.assertEquals(ExpressionType.LONG, eval.type());
+ }
+
@Test
public void testParseJsonTryParseJson() throws JsonProcessingException
{
diff --git
a/sql/src/main/java/org/apache/druid/sql/calcite/expression/builtin/NestedDataOperatorConversions.java
b/sql/src/main/java/org/apache/druid/sql/calcite/expression/builtin/NestedDataOperatorConversions.java
index bfa8f47e56e..a51a3d71375 100644
---
a/sql/src/main/java/org/apache/druid/sql/calcite/expression/builtin/NestedDataOperatorConversions.java
+++
b/sql/src/main/java/org/apache/druid/sql/calcite/expression/builtin/NestedDataOperatorConversions.java
@@ -48,11 +48,13 @@ import org.apache.druid.java.util.common.IAE;
import org.apache.druid.java.util.common.StringUtils;
import org.apache.druid.math.expr.Expr;
import org.apache.druid.math.expr.InputBindings;
+import org.apache.druid.query.expression.NestedDataExpressions;
import org.apache.druid.segment.column.ColumnType;
import org.apache.druid.segment.column.RowSignature;
import org.apache.druid.segment.nested.NestedPathFinder;
import org.apache.druid.segment.nested.NestedPathPart;
import org.apache.druid.segment.virtual.NestedFieldVirtualColumn;
+import org.apache.druid.sql.calcite.expression.DirectOperatorConversion;
import org.apache.druid.sql.calcite.expression.DruidExpression;
import org.apache.druid.sql.calcite.expression.Expressions;
import org.apache.druid.sql.calcite.expression.OperatorConversions;
@@ -78,6 +80,16 @@ public class NestedDataOperatorConversions
true
);
+ public static final SqlReturnTypeInference
NESTED_ARRAY_RETURN_TYPE_INFERENCE = opBinding ->
+ opBinding.getTypeFactory().createArrayType(
+ RowSignatures.makeComplexType(
+ opBinding.getTypeFactory(),
+ ColumnType.NESTED_DATA,
+ true
+ ),
+ -1
+ );
+
public static class JsonPathsOperatorConversion implements
SqlOperatorConversion
{
private static final SqlFunction SQL_FUNCTION = OperatorConversions
@@ -231,6 +243,26 @@ public class NestedDataOperatorConversions
}
}
+ public static class JsonQueryArrayOperatorConversion extends
DirectOperatorConversion
+ {
+ private static final SqlFunction SQL_FUNCTION = OperatorConversions
+
.operatorBuilder(StringUtils.toUpperCase(NestedDataExpressions.JsonQueryArrayExprMacro.NAME))
+ .operandTypeChecker(
+ OperandTypes.family(
+ SqlTypeFamily.ANY,
+ SqlTypeFamily.CHARACTER
+ )
+ )
+ .returnTypeInference(NESTED_ARRAY_RETURN_TYPE_INFERENCE)
+ .functionCategory(SqlFunctionCategory.SYSTEM)
+ .build();
+
+ public JsonQueryArrayOperatorConversion()
+ {
+ super(SQL_FUNCTION, NestedDataExpressions.JsonQueryArrayExprMacro.NAME);
+ }
+ }
+
/**
* The {@link org.apache.calcite.sql2rel.StandardConvertletTable} converts
json_value(.. RETURNING type) into
* cast(json_value_any(..), type).
diff --git
a/sql/src/main/java/org/apache/druid/sql/calcite/planner/DruidOperatorTable.java
b/sql/src/main/java/org/apache/druid/sql/calcite/planner/DruidOperatorTable.java
index ef866aa23cb..a1356fde4c9 100644
---
a/sql/src/main/java/org/apache/druid/sql/calcite/planner/DruidOperatorTable.java
+++
b/sql/src/main/java/org/apache/druid/sql/calcite/planner/DruidOperatorTable.java
@@ -339,6 +339,7 @@ public class DruidOperatorTable implements SqlOperatorTable
.add(new
NestedDataOperatorConversions.JsonKeysOperatorConversion())
.add(new
NestedDataOperatorConversions.JsonPathsOperatorConversion())
.add(new
NestedDataOperatorConversions.JsonQueryOperatorConversion())
+ .add(new
NestedDataOperatorConversions.JsonQueryArrayOperatorConversion())
.add(new
NestedDataOperatorConversions.JsonValueAnyOperatorConversion())
.add(new
NestedDataOperatorConversions.JsonValueBigintOperatorConversion())
.add(new
NestedDataOperatorConversions.JsonValueDoubleOperatorConversion())
diff --git
a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteNestedDataQueryTest.java
b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteNestedDataQueryTest.java
index cea1bdbef7e..8261694df5b 100644
---
a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteNestedDataQueryTest.java
+++
b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteNestedDataQueryTest.java
@@ -6523,4 +6523,218 @@ public class CalciteNestedDataQueryTest extends
BaseCalciteQueryTest
);
}
+
+ @Test
+ public void testJsonQueryArrays()
+ {
+ cannotVectorize();
+ testBuilder()
+ .sql("SELECT JSON_QUERY_ARRAY(arrayObject, '$') FROM druid.arrays")
+ .queryContext(QUERY_CONTEXT_DEFAULT)
+ .expectedQueries(
+ ImmutableList.of(
+ Druids.newScanQueryBuilder()
+ .dataSource(DATA_SOURCE_ARRAYS)
+ .intervals(querySegmentSpec(Filtration.eternity()))
+ .virtualColumns(
+ expressionVirtualColumn("v0",
"json_query_array(\"arrayObject\",'$')",
ColumnType.ofArray(ColumnType.NESTED_DATA))
+ )
+ .columns("v0")
+ .context(QUERY_CONTEXT_DEFAULT)
+ .legacy(false)
+
.resultFormat(ScanQuery.ResultFormat.RESULT_FORMAT_COMPACTED_LIST)
+ .build()
+ )
+ )
+ .expectedResults(
+ ImmutableList.of(
+ new Object[]{"[{\"x\":1000},{\"y\":2000}]"},
+ new Object[]{"[{\"x\":1},{\"x\":2}]"},
+ new Object[]{"[{\"x\":null},{\"x\":2}]"},
+ new Object[]{"[{\"a\":1},{\"b\":2}]"},
+ new Object[]{"[{\"x\":1},{\"x\":2}]"},
+ new Object[]{"[null,{\"x\":2}]"},
+ new Object[]{"[{\"x\":3},{\"x\":4}]"},
+ new Object[]{"[{\"x\":1000},{\"y\":2000}]"},
+ new Object[]{"[{\"x\":1},{\"x\":2}]"},
+ new Object[]{"[{\"x\":null},{\"x\":2}]"},
+ new Object[]{"[{\"a\":1},{\"b\":2}]"},
+ new Object[]{"[{\"x\":1},{\"x\":2}]"},
+ new Object[]{"[null,{\"x\":2}]"},
+ new Object[]{"[{\"x\":3},{\"x\":4}]"}
+ )
+ )
+ .expectedSignature(
+ RowSignature.builder()
+ .add("EXPR$0",
ColumnType.ofArray(ColumnType.NESTED_DATA))
+ .build()
+ )
+ .run();
+ }
+ @Test
+ public void testUnnestJsonQueryArrays()
+ {
+ cannotVectorize();
+ testBuilder()
+ .sql("SELECT objects FROM druid.arrays,
UNNEST(JSON_QUERY_ARRAY(arrayObject, '$')) as u(objects)")
+ .queryContext(QUERY_CONTEXT_NO_STRINGIFY_ARRAY)
+ .expectedQueries(
+ ImmutableList.of(
+ Druids.newScanQueryBuilder()
+ .dataSource(
+ UnnestDataSource.create(
+ TableDataSource.create(DATA_SOURCE_ARRAYS),
+ expressionVirtualColumn("j0.unnest",
"json_query_array(\"arrayObject\",'$')",
ColumnType.ofArray(ColumnType.NESTED_DATA)),
+ null
+ )
+ )
+ .intervals(querySegmentSpec(Filtration.eternity()))
+ .columns("j0.unnest")
+ .context(QUERY_CONTEXT_NO_STRINGIFY_ARRAY)
+ .legacy(false)
+
.resultFormat(ScanQuery.ResultFormat.RESULT_FORMAT_COMPACTED_LIST)
+ .build()
+ )
+ )
+ .expectedResults(
+ ImmutableList.of(
+ new Object[]{"{\"x\":1000}"},
+ new Object[]{"{\"y\":2000}"},
+ new Object[]{"{\"x\":1}"},
+ new Object[]{"{\"x\":2}"},
+ new Object[]{"{\"x\":null}"},
+ new Object[]{"{\"x\":2}"},
+ new Object[]{"{\"a\":1}"},
+ new Object[]{"{\"b\":2}"},
+ new Object[]{"{\"x\":1}"},
+ new Object[]{"{\"x\":2}"},
+ new Object[]{null},
+ new Object[]{"{\"x\":2}"},
+ new Object[]{"{\"x\":3}"},
+ new Object[]{"{\"x\":4}"},
+ new Object[]{"{\"x\":1000}"},
+ new Object[]{"{\"y\":2000}"},
+ new Object[]{"{\"x\":1}"},
+ new Object[]{"{\"x\":2}"},
+ new Object[]{"{\"x\":null}"},
+ new Object[]{"{\"x\":2}"},
+ new Object[]{"{\"a\":1}"},
+ new Object[]{"{\"b\":2}"},
+ new Object[]{"{\"x\":1}"},
+ new Object[]{"{\"x\":2}"},
+ new Object[]{null},
+ new Object[]{"{\"x\":2}"},
+ new Object[]{"{\"x\":3}"},
+ new Object[]{"{\"x\":4}"}
+ )
+ )
+ .expectedSignature(
+ RowSignature.builder()
+ .add("objects", ColumnType.NESTED_DATA)
+ .build()
+ )
+ .run();
+ }
+
+ @Test
+ public void testUnnestJsonQueryArraysJsonValue()
+ {
+ cannotVectorize();
+ testBuilder()
+ .sql(
+ "SELECT"
+ + " json_value(objects, '$.x' returning bigint) as x,"
+ + " count(*)"
+ + " FROM druid.arrays, UNNEST(JSON_QUERY_ARRAY(arrayObject, '$'))
as u(objects)"
+ + " GROUP BY 1"
+ )
+ .queryContext(QUERY_CONTEXT_NO_STRINGIFY_ARRAY)
+ .expectedQueries(
+ ImmutableList.of(
+ GroupByQuery.builder()
+ .setDataSource(
+ UnnestDataSource.create(
+ TableDataSource.create(DATA_SOURCE_ARRAYS),
+ expressionVirtualColumn("j0.unnest",
"json_query_array(\"arrayObject\",'$')",
ColumnType.ofArray(ColumnType.NESTED_DATA)),
+ null
+ )
+ )
+
.setInterval(querySegmentSpec(Filtration.eternity()))
+ .setGranularity(Granularities.ALL)
+ .setVirtualColumns(
+ new NestedFieldVirtualColumn("j0.unnest",
"$.x", "v0", ColumnType.LONG)
+ )
+ .setDimensions(
+ dimensions(
+ new DefaultDimensionSpec("v0", "d0",
ColumnType.LONG)
+ )
+ )
+ .setAggregatorSpecs(new
CountAggregatorFactory("a0"))
+ .setContext(QUERY_CONTEXT_DEFAULT)
+ .build()
+ )
+ )
+ .expectedResults(
+ ImmutableList.of(
+ new Object[]{NullHandling.defaultLongValue(), 10L},
+ new Object[]{1L, 4L},
+ new Object[]{2L, 8L},
+ new Object[]{3L, 2L},
+ new Object[]{4L, 2L},
+ new Object[]{1000L, 2L}
+ )
+ )
+ .expectedSignature(
+ RowSignature.builder()
+ .add("x", ColumnType.LONG)
+ .add("EXPR$1", ColumnType.LONG)
+ .build()
+ )
+ .run();
+ }
+
+ @Test
+ public void testUnnestJsonQueryArraysJsonValueSum()
+ {
+ cannotVectorize();
+ testBuilder()
+ .sql(
+ "SELECT"
+ + " sum(json_value(objects, '$.x' returning bigint)) as xs"
+ + " FROM druid.arrays, UNNEST(JSON_QUERY_ARRAY(arrayObject, '$'))
as u(objects)"
+ )
+ .queryContext(QUERY_CONTEXT_NO_STRINGIFY_ARRAY)
+ .expectedQueries(
+ ImmutableList.of(
+ Druids.newTimeseriesQueryBuilder()
+ .intervals(querySegmentSpec(Filtration.eternity()))
+ .dataSource(
+ UnnestDataSource.create(
+ TableDataSource.create(DATA_SOURCE_ARRAYS),
+ expressionVirtualColumn("j0.unnest",
"json_query_array(\"arrayObject\",'$')",
ColumnType.ofArray(ColumnType.NESTED_DATA)),
+ null
+ )
+ )
+ .virtualColumns(
+ new NestedFieldVirtualColumn("j0.unnest", "$.x",
"v0", ColumnType.LONG)
+ )
+ .aggregators(
+ new LongSumAggregatorFactory("a0", "v0")
+ )
+ .context(QUERY_CONTEXT_DEFAULT)
+ .build()
+ )
+ )
+ .expectedResults(
+ ImmutableList.of(
+ new Object[]{2034L}
+ )
+ )
+ .expectedSignature(
+ RowSignature.builder()
+ .add("xs", ColumnType.LONG)
+ .build()
+ )
+ .run();
+ }
}
diff --git a/website/.spelling b/website/.spelling
index e5b8f3cb494..b23c250c8ba 100644
--- a/website/.spelling
+++ b/website/.spelling
@@ -361,6 +361,7 @@ json_keys
json_object
json_paths
json_query
+json_query_array
json_value
karlkfi
kerberos
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]