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 27215d1ff1 fix complex_decode_base64 function, add SQL bindings
(#13332)
27215d1ff1 is described below
commit 27215d1ff13ffc06d58fe06a59e2b4c32c1afc75
Author: Clint Wylie <[email protected]>
AuthorDate: Wed Nov 9 23:40:25 2022 -0800
fix complex_decode_base64 function, add SQL bindings (#13332)
* fix complex_decode_base64 function, add SQL bindings
* more permissive
---
.../apache/druid/math/expr/BuiltInExprMacros.java | 147 +++++++++++++++++++++
.../org/apache/druid/math/expr/ExprMacroTable.java | 13 +-
.../java/org/apache/druid/math/expr/Function.java | 74 -----------
.../apache/druid/segment/column/TypeStrategy.java | 15 ++-
.../org/apache/druid/math/expr/FunctionTest.java | 15 ++-
.../druid/segment/column/TypeStrategiesTest.java | 6 +
.../hll/sql/HllSketchBaseSqlAggregator.java | 27 ++--
.../column/ObjectStrategyComplexTypeStrategy.java | 7 +
.../apache/druid/segment/serde/ComplexMetrics.java | 2 +-
.../BuiltinApproxCountDistinctSqlAggregator.java | 23 +++-
.../ComplexDecodeBase64OperatorConversion.java | 95 +++++++++++++
.../sql/calcite/planner/DruidOperatorTable.java | 2 +
.../apache/druid/sql/calcite/CalciteQueryTest.java | 67 ++++++++++
13 files changed, 395 insertions(+), 98 deletions(-)
diff --git
a/core/src/main/java/org/apache/druid/math/expr/BuiltInExprMacros.java
b/core/src/main/java/org/apache/druid/math/expr/BuiltInExprMacros.java
new file mode 100644
index 0000000000..e47ef742c3
--- /dev/null
+++ b/core/src/main/java/org/apache/druid/math/expr/BuiltInExprMacros.java
@@ -0,0 +1,147 @@
+/*
+ * 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.druid.math.expr;
+
+import org.apache.druid.java.util.common.StringUtils;
+import org.apache.druid.segment.column.TypeStrategy;
+
+import javax.annotation.Nullable;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class BuiltInExprMacros
+{
+ public static class ComplexDecodeBase64ExprMacro implements
ExprMacroTable.ExprMacro
+ {
+ public static final String NAME = "complex_decode_base64";
+
+ @Override
+ public String name()
+ {
+ return NAME;
+ }
+
+ @Override
+ public Expr apply(List<Expr> args)
+ {
+ return new ComplexDecodeBase64Expression(args);
+ }
+
+ final class ComplexDecodeBase64Expression extends
ExprMacroTable.BaseScalarMacroFunctionExpr
+ {
+ private final ExpressionType complexType;
+ private final TypeStrategy<?> typeStrategy;
+
+ public ComplexDecodeBase64Expression(List<Expr> args)
+ {
+ super(NAME, args);
+ validationHelperCheckArgumentCount(args, 2);
+ final Expr arg0 = args.get(0);
+
+ if (!arg0.isLiteral()) {
+ throw validationFailed(
+ "first argument must be constant STRING expression containing a
valid complex type name but got '%s' instead",
+ arg0.stringify()
+ );
+ }
+ if (arg0.isNullLiteral()) {
+ throw validationFailed("first argument must be constant STRING
expression containing a valid complex type name but got NULL instead");
+ }
+ final Object literal = arg0.getLiteralValue();
+ if (!(literal instanceof String)) {
+ throw validationFailed(
+ "first argument must be constant STRING expression containing a
valid complex type name but got '%s' instead",
+ arg0.getLiteralValue()
+ );
+ }
+
+ this.complexType =
ExpressionTypeFactory.getInstance().ofComplex((String) literal);
+ try {
+ this.typeStrategy = complexType.getStrategy();
+ }
+ catch (IllegalArgumentException illegal) {
+ throw validationFailed(
+ "first argument must be a valid COMPLEX type name, got unknown
COMPLEX type [%s]",
+ complexType.asTypeString()
+ );
+ }
+ }
+
+ @Override
+ public ExprEval<?> eval(ObjectBinding bindings)
+ {
+ ExprEval<?> toDecode = args.get(1).eval(bindings);
+ if (toDecode.value() == null) {
+ return ExprEval.ofComplex(complexType, null);
+ }
+ final Object serializedValue = toDecode.value();
+ final byte[] base64;
+ if (serializedValue instanceof String) {
+ base64 = StringUtils.decodeBase64String(toDecode.asString());
+ } else if (serializedValue instanceof byte[]) {
+ base64 = (byte[]) serializedValue;
+ } else if
(complexType.getComplexTypeName().equals(toDecode.type().getComplexTypeName()))
{
+ // pass it through, it is already the right thing
+ return toDecode;
+ } else {
+ throw validationFailed(
+ "second argument must be a base64 encoded STRING value but got
%s instead",
+ toDecode.type()
+ );
+ }
+
+ return ExprEval.ofComplex(complexType, typeStrategy.fromBytes(base64));
+ }
+
+ @Override
+ public Expr visit(Shuttle shuttle)
+ {
+ List<Expr> newArgs = args.stream().map(x ->
x.visit(shuttle)).collect(Collectors.toList());
+ return shuttle.visit(new ComplexDecodeBase64Expression(newArgs));
+ }
+
+ @Nullable
+ @Override
+ public ExpressionType getOutputType(InputBindingInspector inspector)
+ {
+ return complexType;
+ }
+
+ @Override
+ public boolean isLiteral()
+ {
+ return args.get(1).isLiteral();
+ }
+
+ @Override
+ public boolean isNullLiteral()
+ {
+ return args.get(1).isNullLiteral();
+ }
+
+ @Nullable
+ @Override
+ public Object getLiteralValue()
+ {
+ return eval(InputBindings.nilBindings()).value();
+ }
+ }
+ }
+}
diff --git a/core/src/main/java/org/apache/druid/math/expr/ExprMacroTable.java
b/core/src/main/java/org/apache/druid/math/expr/ExprMacroTable.java
index ec7c2036c9..6f11ff3e69 100644
--- a/core/src/main/java/org/apache/druid/math/expr/ExprMacroTable.java
+++ b/core/src/main/java/org/apache/druid/math/expr/ExprMacroTable.java
@@ -23,6 +23,7 @@ import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.apache.druid.java.util.common.StringUtils;
@@ -42,18 +43,18 @@ import java.util.stream.Collectors;
*/
public class ExprMacroTable
{
+ private static final List<ExprMacro> BUILT_IN = ImmutableList.of(
+ new BuiltInExprMacros.ComplexDecodeBase64ExprMacro()
+ );
private static final ExprMacroTable NIL = new
ExprMacroTable(Collections.emptyList());
private final Map<String, ExprMacro> macroMap;
public ExprMacroTable(final List<ExprMacro> macros)
{
- this.macroMap = macros.stream().collect(
- Collectors.toMap(
- m -> StringUtils.toLowerCase(m.name()),
- m -> m
- )
- );
+ this.macroMap = Maps.newHashMapWithExpectedSize(BUILT_IN.size() +
macros.size());
+ macroMap.putAll(BUILT_IN.stream().collect(Collectors.toMap(m ->
StringUtils.toLowerCase(m.name()), m -> m)));
+ macroMap.putAll(macros.stream().collect(Collectors.toMap(m ->
StringUtils.toLowerCase(m.name()), m -> m)));
}
public static ExprMacroTable nil()
diff --git a/core/src/main/java/org/apache/druid/math/expr/Function.java
b/core/src/main/java/org/apache/druid/math/expr/Function.java
index 62e7ab9b80..1763e18ef8 100644
--- a/core/src/main/java/org/apache/druid/math/expr/Function.java
+++ b/core/src/main/java/org/apache/druid/math/expr/Function.java
@@ -31,7 +31,6 @@ import org.apache.druid.math.expr.vector.VectorMathProcessors;
import org.apache.druid.math.expr.vector.VectorProcessors;
import org.apache.druid.math.expr.vector.VectorStringProcessors;
import org.apache.druid.segment.column.TypeSignature;
-import org.apache.druid.segment.column.TypeStrategy;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.format.DateTimeFormat;
@@ -39,7 +38,6 @@ import org.joda.time.format.DateTimeFormat;
import javax.annotation.Nullable;
import java.math.BigDecimal;
import java.math.RoundingMode;
-import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -3683,76 +3681,4 @@ public interface Function extends NamedFunction
return HumanReadableBytes.UnitSystem.DECIMAL;
}
}
-
- class ComplexDecodeBase64Function implements Function
- {
- @Override
- public String name()
- {
- return "complex_decode_base64";
- }
-
- @Override
- public ExprEval apply(List<Expr> args, Expr.ObjectBinding bindings)
- {
- ExprEval arg0 = args.get(0).eval(bindings);
- if (!arg0.type().is(ExprType.STRING)) {
- throw validationFailed(
- "first argument must be constant STRING expression containing a
valid complex type name but got %s instead",
- arg0.type()
- );
- }
- ExpressionType type =
ExpressionTypeFactory.getInstance().ofComplex((String)
args.get(0).getLiteralValue());
- TypeStrategy strategy;
- try {
- strategy = type.getStrategy();
- }
- catch (IllegalArgumentException illegal) {
- throw validationFailed(
- "first argument must be a valid COMPLEX type name, got unknown
COMPLEX type [%s]",
- type.asTypeString()
- );
- }
- ExprEval base64String = args.get(1).eval(bindings);
- if (!base64String.type().is(ExprType.STRING)) {
- throw validationFailed(
- "second argument must be a base64 encoded STRING value but got %s
instead",
- base64String.type()
- );
- }
- if (base64String.value() == null) {
- return ExprEval.ofComplex(type, null);
- }
-
- final byte[] base64 =
StringUtils.decodeBase64String(base64String.asString());
- return ExprEval.ofComplex(type, strategy.read(ByteBuffer.wrap(base64)));
- }
-
- @Override
- public void validateArguments(List<Expr> args)
- {
- validationHelperCheckArgumentCount(args, 2);
- if (!args.get(0).isLiteral() || args.get(0).isNullLiteral()) {
- throw validationFailed(
- "first argument must be constant STRING expression containing a
valid COMPLEX type name"
- );
- }
- }
-
- @Nullable
- @Override
- public ExpressionType getOutputType(
- Expr.InputBindingInspector inspector,
- List<Expr> args
- )
- {
- ExpressionType arg0Type = args.get(0).getOutputType(inspector);
- if (arg0Type == null || !arg0Type.is(ExprType.STRING)) {
- throw validationFailed(
- "first argument must be constant STRING expression containing a
valid COMPLEX type name"
- );
- }
- return ExpressionTypeFactory.getInstance().ofComplex((String)
args.get(0).getLiteralValue());
- }
- }
}
diff --git
a/core/src/main/java/org/apache/druid/segment/column/TypeStrategy.java
b/core/src/main/java/org/apache/druid/segment/column/TypeStrategy.java
index 4ac575469e..8a97882d54 100644
--- a/core/src/main/java/org/apache/druid/segment/column/TypeStrategy.java
+++ b/core/src/main/java/org/apache/druid/segment/column/TypeStrategy.java
@@ -68,7 +68,6 @@ public interface TypeStrategy<T> extends Comparator<T>
*/
int estimateSizeBytes(T value);
-
/**
* Read a non-null value from the {@link ByteBuffer} at the current {@link
ByteBuffer#position()}. This will move
* the underlying position by the size of the value read.
@@ -150,4 +149,18 @@ public interface TypeStrategy<T> extends Comparator<T>
buffer.position(oldPosition);
}
}
+
+ /**
+ * Translate raw byte array into a value. This is primarily useful for
transforming self contained values that are
+ * serialized into byte arrays, such as happens with 'COMPLEX' types which
serialize to base64 strings in JSON
+ * responses.
+ *
+ * 'COMPLEX' types should implement this method to participate in the
expression systems built-in function
+ * to deserialize base64 encoded values,
+ * {@link
org.apache.druid.math.expr.BuiltInExprMacros.ComplexDecodeBase64ExprMacro}.
+ */
+ default T fromBytes(byte[] value)
+ {
+ throw new IllegalStateException("Not supported");
+ }
}
diff --git a/core/src/test/java/org/apache/druid/math/expr/FunctionTest.java
b/core/src/test/java/org/apache/druid/math/expr/FunctionTest.java
index 0f9bba6652..acf66972a1 100644
--- a/core/src/test/java/org/apache/druid/math/expr/FunctionTest.java
+++ b/core/src/test/java/org/apache/druid/math/expr/FunctionTest.java
@@ -924,12 +924,25 @@ public class FunctionTest extends
InitializedNullHandlingTest
);
}
+ @Test
+ public void testComplexDecodeBaseArg0Null()
+ {
+ expectedException.expect(ExpressionValidationException.class);
+ expectedException.expectMessage(
+ "Function[complex_decode_base64] first argument must be constant
STRING expression containing a valid complex type name but got NULL instead"
+ );
+ assertExpr(
+ "complex_decode_base64(null, string)",
+ null
+ );
+ }
+
@Test
public void testComplexDecodeBaseArg0BadType()
{
expectedException.expect(ExpressionValidationException.class);
expectedException.expectMessage(
- "Function[complex_decode_base64] first argument must be constant
STRING expression containing a valid complex type name but got LONG instead"
+ "Function[complex_decode_base64] first argument must be constant
STRING expression containing a valid complex type name but got '1' instead"
);
assertExpr(
"complex_decode_base64(1, string)",
diff --git
a/core/src/test/java/org/apache/druid/segment/column/TypeStrategiesTest.java
b/core/src/test/java/org/apache/druid/segment/column/TypeStrategiesTest.java
index 0e006904ca..fa6d86d21f 100644
--- a/core/src/test/java/org/apache/druid/segment/column/TypeStrategiesTest.java
+++ b/core/src/test/java/org/apache/druid/segment/column/TypeStrategiesTest.java
@@ -681,5 +681,11 @@ public class TypeStrategiesTest
}
return written;
}
+
+ @Override
+ public NullableLongPair fromBytes(byte[] value)
+ {
+ return read(ByteBuffer.wrap(value));
+ }
}
}
diff --git
a/extensions-core/datasketches/src/main/java/org/apache/druid/query/aggregation/datasketches/hll/sql/HllSketchBaseSqlAggregator.java
b/extensions-core/datasketches/src/main/java/org/apache/druid/query/aggregation/datasketches/hll/sql/HllSketchBaseSqlAggregator.java
index 0266f1ed5e..665ed7c39f 100644
---
a/extensions-core/datasketches/src/main/java/org/apache/druid/query/aggregation/datasketches/hll/sql/HllSketchBaseSqlAggregator.java
+++
b/extensions-core/datasketches/src/main/java/org/apache/druid/query/aggregation/datasketches/hll/sql/HllSketchBaseSqlAggregator.java
@@ -160,14 +160,25 @@ public abstract class HllSketchBaseSqlAggregator
implements SqlAggregator
dimensionSpec = new DefaultDimensionSpec(virtualColumnName, null,
inputType);
}
- aggregatorFactory = new HllSketchBuildAggregatorFactory(
- aggregatorName,
- dimensionSpec.getDimension(),
- logK,
- tgtHllType,
- finalizeSketch ||
SketchQueryContext.isFinalizeOuterSketches(plannerContext),
- ROUND
- );
+ if (inputType.is(ValueType.COMPLEX)) {
+ aggregatorFactory = new HllSketchMergeAggregatorFactory(
+ aggregatorName,
+ dimensionSpec.getOutputName(),
+ logK,
+ tgtHllType,
+ finalizeSketch ||
SketchQueryContext.isFinalizeOuterSketches(plannerContext),
+ ROUND
+ );
+ } else {
+ aggregatorFactory = new HllSketchBuildAggregatorFactory(
+ aggregatorName,
+ dimensionSpec.getDimension(),
+ logK,
+ tgtHllType,
+ finalizeSketch ||
SketchQueryContext.isFinalizeOuterSketches(plannerContext),
+ ROUND
+ );
+ }
}
return toAggregation(
diff --git
a/processing/src/main/java/org/apache/druid/segment/column/ObjectStrategyComplexTypeStrategy.java
b/processing/src/main/java/org/apache/druid/segment/column/ObjectStrategyComplexTypeStrategy.java
index 91ba3803fe..351f2665d0 100644
---
a/processing/src/main/java/org/apache/druid/segment/column/ObjectStrategyComplexTypeStrategy.java
+++
b/processing/src/main/java/org/apache/druid/segment/column/ObjectStrategyComplexTypeStrategy.java
@@ -54,6 +54,7 @@ public class ObjectStrategyComplexTypeStrategy<T> implements
TypeStrategy<T>
{
final int complexLength = buffer.getInt();
ByteBuffer dupe = buffer.duplicate();
+ dupe.order(buffer.order());
dupe.limit(dupe.position() + complexLength);
return objectStrategy.fromByteBuffer(dupe, complexLength);
}
@@ -85,4 +86,10 @@ public class ObjectStrategyComplexTypeStrategy<T> implements
TypeStrategy<T>
{
return objectStrategy.compare(o1, o2);
}
+
+ @Override
+ public T fromBytes(byte[] value)
+ {
+ return objectStrategy.fromByteBuffer(ByteBuffer.wrap(value), value.length);
+ }
}
diff --git
a/processing/src/main/java/org/apache/druid/segment/serde/ComplexMetrics.java
b/processing/src/main/java/org/apache/druid/segment/serde/ComplexMetrics.java
index d917df4a84..1ca6e0b43a 100644
---
a/processing/src/main/java/org/apache/druid/segment/serde/ComplexMetrics.java
+++
b/processing/src/main/java/org/apache/druid/segment/serde/ComplexMetrics.java
@@ -53,6 +53,7 @@ public class ComplexMetrics
{
COMPLEX_SERIALIZERS.compute(type, (key, value) -> {
if (value == null) {
+ TypeStrategies.registerComplex(type, serde.getTypeStrategy());
return serde;
} else {
if (!value.getClass().getName().equals(serde.getClass().getName())) {
@@ -63,7 +64,6 @@ public class ComplexMetrics
value.getClass().getName()
);
} else {
- TypeStrategies.registerComplex(type, serde.getTypeStrategy());
return value;
}
}
diff --git
a/sql/src/main/java/org/apache/druid/sql/calcite/aggregation/builtin/BuiltinApproxCountDistinctSqlAggregator.java
b/sql/src/main/java/org/apache/druid/sql/calcite/aggregation/builtin/BuiltinApproxCountDistinctSqlAggregator.java
index 7482a728f2..9ab114c876 100644
---
a/sql/src/main/java/org/apache/druid/sql/calcite/aggregation/builtin/BuiltinApproxCountDistinctSqlAggregator.java
+++
b/sql/src/main/java/org/apache/druid/sql/calcite/aggregation/builtin/BuiltinApproxCountDistinctSqlAggregator.java
@@ -121,13 +121,22 @@ public class BuiltinApproxCountDistinctSqlAggregator
implements SqlAggregator
dimensionSpec = new DefaultDimensionSpec(virtualColumnName, null,
inputType);
}
- aggregatorFactory = new CardinalityAggregatorFactory(
- aggregatorName,
- null,
- ImmutableList.of(dimensionSpec),
- false,
- true
- );
+ if (inputType.is(ValueType.COMPLEX)) {
+ aggregatorFactory = new HyperUniquesAggregatorFactory(
+ aggregatorName,
+ dimensionSpec.getOutputName(),
+ false,
+ true
+ );
+ } else {
+ aggregatorFactory = new CardinalityAggregatorFactory(
+ aggregatorName,
+ null,
+ ImmutableList.of(dimensionSpec),
+ false,
+ true
+ );
+ }
}
return Aggregation.create(
diff --git
a/sql/src/main/java/org/apache/druid/sql/calcite/expression/builtin/ComplexDecodeBase64OperatorConversion.java
b/sql/src/main/java/org/apache/druid/sql/calcite/expression/builtin/ComplexDecodeBase64OperatorConversion.java
new file mode 100644
index 0000000000..0822e15750
--- /dev/null
+++
b/sql/src/main/java/org/apache/druid/sql/calcite/expression/builtin/ComplexDecodeBase64OperatorConversion.java
@@ -0,0 +1,95 @@
+/*
+ * 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.druid.sql.calcite.expression.builtin;
+
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.sql.SqlFunction;
+import org.apache.calcite.sql.SqlFunctionCategory;
+import org.apache.calcite.sql.SqlOperator;
+import org.apache.calcite.sql.type.OperandTypes;
+import org.apache.calcite.sql.type.SqlReturnTypeInference;
+import org.apache.calcite.sql.type.SqlTypeFamily;
+import org.apache.druid.java.util.common.StringUtils;
+import org.apache.druid.math.expr.BuiltInExprMacros;
+import org.apache.druid.segment.column.ColumnType;
+import org.apache.druid.segment.column.RowSignature;
+import org.apache.druid.sql.calcite.expression.DruidExpression;
+import org.apache.druid.sql.calcite.expression.OperatorConversions;
+import org.apache.druid.sql.calcite.expression.SqlOperatorConversion;
+import org.apache.druid.sql.calcite.planner.PlannerContext;
+import org.apache.druid.sql.calcite.table.RowSignatures;
+
+import javax.annotation.Nullable;
+
+public class ComplexDecodeBase64OperatorConversion implements
SqlOperatorConversion
+{
+
+ public static final SqlReturnTypeInference
ARBITRARY_COMPLEX_RETURN_TYPE_INFERENCE = opBinding -> {
+ String typeName = opBinding.getOperandLiteralValue(0, String.class);
+ return RowSignatures.makeComplexType(
+ opBinding.getTypeFactory(),
+ ColumnType.ofComplex(typeName),
+ true
+ );
+ };
+
+ private static final SqlFunction SQL_FUNCTION = OperatorConversions
+
.operatorBuilder(StringUtils.toUpperCase(BuiltInExprMacros.ComplexDecodeBase64ExprMacro.NAME))
+ .operandTypeChecker(
+ OperandTypes.sequence(
+ "(typeName,base64)",
+ OperandTypes.and(OperandTypes.family(SqlTypeFamily.STRING),
OperandTypes.LITERAL),
+ OperandTypes.ANY
+ )
+ )
+ .returnTypeInference(ARBITRARY_COMPLEX_RETURN_TYPE_INFERENCE)
+ .functionCategory(SqlFunctionCategory.USER_DEFINED_FUNCTION)
+ .build();
+
+
+ @Override
+ public SqlOperator calciteOperator()
+ {
+ return SQL_FUNCTION;
+ }
+
+ @Nullable
+ @Override
+ public DruidExpression toDruidExpression(
+ PlannerContext plannerContext,
+ RowSignature rowSignature,
+ RexNode rexNode
+ )
+ {
+ return OperatorConversions.convertCall(
+ plannerContext,
+ rowSignature,
+ rexNode,
+ druidExpressions -> {
+ String arg0 = druidExpressions.get(0).getExpression();
+ return DruidExpression.ofExpression(
+ ColumnType.ofComplex(arg0.substring(1, arg0.length() - 1)),
+
DruidExpression.functionCall(BuiltInExprMacros.ComplexDecodeBase64ExprMacro.NAME),
+ druidExpressions
+ );
+ }
+ );
+ }
+}
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 2b0aa09e45..2431fc4acc 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
@@ -71,6 +71,7 @@ import
org.apache.druid.sql.calcite.expression.builtin.ArrayToStringOperatorConv
import org.apache.druid.sql.calcite.expression.builtin.BTrimOperatorConversion;
import org.apache.druid.sql.calcite.expression.builtin.CastOperatorConversion;
import org.apache.druid.sql.calcite.expression.builtin.CeilOperatorConversion;
+import
org.apache.druid.sql.calcite.expression.builtin.ComplexDecodeBase64OperatorConversion;
import
org.apache.druid.sql.calcite.expression.builtin.ConcatOperatorConversion;
import
org.apache.druid.sql.calcite.expression.builtin.ContainsOperatorConversion;
import
org.apache.druid.sql.calcite.expression.builtin.DateTruncOperatorConversion;
@@ -211,6 +212,7 @@ public class DruidOperatorTable implements SqlOperatorTable
ImmutableList.<SqlOperatorConversion>builder()
.add(new CastOperatorConversion())
.add(new ReinterpretOperatorConversion())
+ .add(new ComplexDecodeBase64OperatorConversion())
.build();
private static final List<SqlOperatorConversion> ARRAY_OPERATOR_CONVERSIONS =
diff --git
a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteQueryTest.java
b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteQueryTest.java
index badb943117..516e363f9b 100644
--- a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteQueryTest.java
+++ b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteQueryTest.java
@@ -14293,4 +14293,71 @@ public class CalciteQueryTest extends
BaseCalciteQueryTest
ImmutableList.of()
);
}
+
+ @Test
+ public void testComplexDecode()
+ {
+ cannotVectorize();
+ testQuery(
+ "SELECT
COMPLEX_DECODE_BASE64('hyperUnique',PARSE_JSON(TO_JSON_STRING(unique_dim1)))
from druid.foo LIMIT 10",
+ ImmutableList.of(
+ Druids.newScanQueryBuilder()
+ .dataSource(CalciteTests.DATASOURCE1)
+ .intervals(querySegmentSpec(Filtration.eternity()))
+ .columns("v0")
+ .virtualColumns(
+ expressionVirtualColumn(
+ "v0",
+
"complex_decode_base64('hyperUnique',parse_json(to_json_string(\"unique_dim1\")))",
+ ColumnType.ofComplex("hyperUnique")
+ )
+ )
+ .resultFormat(ResultFormat.RESULT_FORMAT_COMPACTED_LIST)
+ .legacy(false)
+ .limit(10)
+ .build()
+ ),
+ ImmutableList.of(
+ new Object[]{"\"AQAAAEAAAA==\""},
+ new Object[]{"\"AQAAAQAAAAHNBA==\""},
+ new Object[]{"\"AQAAAQAAAAOzAg==\""},
+ new Object[]{"\"AQAAAQAAAAFREA==\""},
+ new Object[]{"\"AQAAAQAAAACyEA==\""},
+ new Object[]{"\"AQAAAQAAAAEkAQ==\""}
+ )
+ );
+ }
+
+ @Test
+ public void testComplexDecodeAgg()
+ {
+ cannotVectorize();
+ testQuery(
+ "SELECT
APPROX_COUNT_DISTINCT_BUILTIN(COMPLEX_DECODE_BASE64('hyperUnique',PARSE_JSON(TO_JSON_STRING(unique_dim1))))
from druid.foo",
+ ImmutableList.of(
+ Druids.newTimeseriesQueryBuilder()
+ .dataSource(CalciteTests.DATASOURCE1)
+ .intervals(querySegmentSpec(Filtration.eternity()))
+ .virtualColumns(
+ expressionVirtualColumn(
+ "v0",
+
"complex_decode_base64('hyperUnique',parse_json(to_json_string(\"unique_dim1\")))",
+ ColumnType.ofComplex("hyperUnique")
+ )
+ )
+ .aggregators(
+ new HyperUniquesAggregatorFactory(
+ "a0",
+ "v0",
+ false,
+ true
+ )
+ )
+ .build()
+ ),
+ ImmutableList.of(
+ new Object[]{6L}
+ )
+ );
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]