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 <cwy...@apache.org>
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: commits-unsubscr...@druid.apache.org
For additional commands, e-mail: commits-h...@druid.apache.org

Reply via email to