gianm commented on code in PR #12968:
URL: https://github.com/apache/druid/pull/12968#discussion_r955517310


##########
docs/misc/math-expr.md:
##########
@@ -63,7 +63,7 @@ The following built-in functions are available.
 
 |name|description|
 |----|-----------|
-|cast|cast(expr,'LONG' or 'DOUBLE' or 'STRING' or 'LONG_ARRAY', or 
'DOUBLE_ARRAY' or 'STRING_ARRAY') returns expr with specified type. exception 
can be thrown. Scalar types may be cast to array types and will take the form 
of a single element list (null will still be null). |
+|cast|cast(expr,'LONG' or 'DOUBLE' or 'STRING' or 'ARRAY<LONG>', or 
'ARRAY<DOUBLE>' or 'ARRAY<STRING>') returns expr with specified type. exception 
can be thrown. Scalar types may be cast to array types and will take the form 
of a single element list (null will still be null). |

Review Comment:
   Is this a backwards compatibility issue (did something really change) or 
fixing a doc bug?



##########
sql/src/main/java/org/apache/druid/sql/calcite/planner/DruidOperatorTable.java:
##########
@@ -304,8 +304,10 @@ public class DruidOperatorTable implements SqlOperatorTable
                    .add(new 
NestedDataOperatorConversions.JsonKeysOperatorConversion())
                    .add(new 
NestedDataOperatorConversions.JsonPathsOperatorConversion())
                    .add(new 
NestedDataOperatorConversions.JsonQueryOperatorConversion())
-                   .add(new 
NestedDataOperatorConversions.JsonValueOperatorConversion())
                    .add(new 
NestedDataOperatorConversions.JsonValueAnyOperatorConversion())
+                   .add(new 
NestedDataOperatorConversions.JsonValueBigintOperatorConversion())
+                   .add(new 
NestedDataOperatorConversions.JsonValueDoubleOperatorConversion())
+                   .add(new 
NestedDataOperatorConversions.JsonValueStringOperatorConversion())

Review Comment:
   Do these need to be registered with the operator table in order for the 
convertlet to return them? Asking since, if possible, it'd be nice if end users 
couldn't write these functions directly.
   
   Not a requirement, I suppose. If they have to be here then it's fine.



##########
sql/src/main/java/org/apache/druid/sql/calcite/expression/builtin/NestedDataOperatorConversions.java:
##########
@@ -395,24 +465,182 @@ public DruidExpression toDruidExpression(
             )
         );
       }
-      return DruidExpression.ofExpression(ColumnType.STRING, builder, 
druidExpressions);
+      return DruidExpression.ofExpression(druidType, builder, 
druidExpressions);
+    }
+
+    static SqlFunction buildFunction(String functionName, SqlTypeName typeName)
+    {
+      return OperatorConversions.operatorBuilder(functionName)
+                                .operandTypeChecker(
+                                    OperandTypes.sequence(
+                                        "(expr,path)",
+                                        OperandTypes.family(SqlTypeFamily.ANY),
+                                        
OperandTypes.family(SqlTypeFamily.STRING)
+                                    )
+                                )
+                                .returnTypeInference(
+                                    ReturnTypes.cascade(
+                                        opBinding -> 
opBinding.getTypeFactory().createSqlType(typeName),
+                                        SqlTypeTransforms.FORCE_NULLABLE
+                                    )
+                                )
+                                
.functionCategory(SqlFunctionCategory.USER_DEFINED_FUNCTION)
+                                .build();
+    }
+  }
+
+  public static class JsonValueBigintOperatorConversion extends 
JsonValueReturningTypeOperatorConversion
+  {
+    private static final SqlFunction FUNCTION = 
buildFunction("JSON_VALUE_BIGINT", SqlTypeName.BIGINT);
+
+    public JsonValueBigintOperatorConversion()
+    {
+      super(FUNCTION, ColumnType.LONG);
+    }
+  }
+
+  public static class JsonValueDoubleOperatorConversion extends 
JsonValueReturningTypeOperatorConversion
+  {
+    private static final SqlFunction FUNCTION = 
buildFunction("JSON_VALUE_DOUBLE", SqlTypeName.DOUBLE);
+
+    public JsonValueDoubleOperatorConversion()
+    {
+      super(FUNCTION, ColumnType.DOUBLE);
     }
   }
 
-  // calcite converts JSON_VALUE to JSON_VALUE_ANY so we have to wire that up 
too...
-  public static class JsonValueAnyOperatorConversion extends 
AliasedOperatorConversion
+  public static class JsonValueStringOperatorConversion extends 
JsonValueReturningTypeOperatorConversion
   {
-    private static final String FUNCTION_NAME = 
StringUtils.toUpperCase("json_value_any");
+    private static final SqlFunction FUNCTION = 
buildFunction("JSON_VALUE_VARCHAR", SqlTypeName.VARCHAR);
 
-    public JsonValueAnyOperatorConversion()
+    public JsonValueStringOperatorConversion()
     {
-      super(new JsonValueOperatorConversion(), FUNCTION_NAME);
+      super(FUNCTION, ColumnType.STRING);
     }
+  }
+
+  /**
+   * Calcites {@link org.apache.calcite.sql2rel.StandardConvertletTable} 
translates JSON_VALUE

Review Comment:
   Incomplete comment?



##########
processing/src/main/java/org/apache/druid/query/expression/NestedDataExpressions.java:
##########
@@ -442,38 +442,76 @@ public String name()
     public Expr apply(List<Expr> args)
     {
       final List<NestedPathPart> parts = 
getArg1JsonPathPartsFromLiteral(name(), args);
-      class JsonValueExpr extends ExprMacroTable.BaseScalarMacroFunctionExpr
-      {
-        public JsonValueExpr(List<Expr> args)
-        {
-          super(name(), args);
+      if (args.size() == 3 && args.get(2).isLiteral()) {
+        final ExpressionType castTo = ExpressionType.fromString((String) 
args.get(2).getLiteralValue());
+        if (castTo == null) {
+          throw new IAE("Invalid output type: [%s]", 
args.get(2).getLiteralValue());
         }
-
-        @Override
-        public ExprEval eval(ObjectBinding bindings)
+        class CastJsonValueExpr extends 
ExprMacroTable.BaseScalarMacroFunctionExpr
         {
-          ExprEval input = args.get(0).eval(bindings);
-          return ExprEval.bestEffortOf(
-              NestedPathFinder.findLiteral(unwrap(input), parts)
-          );
-        }
+          public CastJsonValueExpr(List<Expr> args)
+          {
+            super(name(), args);
+          }
 
-        @Override
-        public Expr visit(Shuttle shuttle)
-        {
-          List<Expr> newArgs = args.stream().map(x -> 
x.visit(shuttle)).collect(Collectors.toList());
-          return shuttle.visit(new JsonValueExpr(newArgs));
-        }
+          @Override
+          public ExprEval eval(ObjectBinding bindings)
+          {
+            ExprEval input = args.get(0).eval(bindings);
+            return ExprEval.bestEffortOf(
+                NestedPathFinder.findLiteral(unwrap(input), parts)
+            ).castTo(castTo);
+          }
 
-        @Nullable
-        @Override
-        public ExpressionType getOutputType(InputBindingInspector inspector)
+          @Override
+          public Expr visit(Shuttle shuttle)
+          {
+            List<Expr> newArgs = args.stream().map(x -> 
x.visit(shuttle)).collect(Collectors.toList());
+            return shuttle.visit(new CastJsonValueExpr(newArgs));
+          }
+
+          @Nullable
+          @Override
+          public ExpressionType getOutputType(InputBindingInspector inspector)
+          {
+            return castTo;
+          }
+        }
+        return new CastJsonValueExpr(args);
+      } else {
+        class JsonValueExpr extends ExprMacroTable.BaseScalarMacroFunctionExpr
         {
-          // we cannot infer the output type (well we could say it is 'STRING' 
right now because is all we support...
-          return null;
+          public JsonValueExpr(List<Expr> args)

Review Comment:
   Seems like the logic is the same between `get_path` and the no-cast version 
of `json_value`. Can we share the code too? Maybe use the same static class for 
both?



##########
sql/src/main/java/org/apache/druid/sql/calcite/expression/builtin/NestedDataOperatorConversions.java:
##########
@@ -395,24 +465,182 @@ public DruidExpression toDruidExpression(
             )
         );
       }
-      return DruidExpression.ofExpression(ColumnType.STRING, builder, 
druidExpressions);
+      return DruidExpression.ofExpression(druidType, builder, 
druidExpressions);
+    }
+
+    static SqlFunction buildFunction(String functionName, SqlTypeName typeName)
+    {
+      return OperatorConversions.operatorBuilder(functionName)
+                                .operandTypeChecker(
+                                    OperandTypes.sequence(
+                                        "(expr,path)",
+                                        OperandTypes.family(SqlTypeFamily.ANY),
+                                        
OperandTypes.family(SqlTypeFamily.STRING)
+                                    )
+                                )
+                                .returnTypeInference(
+                                    ReturnTypes.cascade(
+                                        opBinding -> 
opBinding.getTypeFactory().createSqlType(typeName),
+                                        SqlTypeTransforms.FORCE_NULLABLE
+                                    )
+                                )
+                                
.functionCategory(SqlFunctionCategory.USER_DEFINED_FUNCTION)
+                                .build();
+    }
+  }
+
+  public static class JsonValueBigintOperatorConversion extends 
JsonValueReturningTypeOperatorConversion
+  {
+    private static final SqlFunction FUNCTION = 
buildFunction("JSON_VALUE_BIGINT", SqlTypeName.BIGINT);
+
+    public JsonValueBigintOperatorConversion()
+    {
+      super(FUNCTION, ColumnType.LONG);
+    }
+  }
+
+  public static class JsonValueDoubleOperatorConversion extends 
JsonValueReturningTypeOperatorConversion
+  {
+    private static final SqlFunction FUNCTION = 
buildFunction("JSON_VALUE_DOUBLE", SqlTypeName.DOUBLE);
+
+    public JsonValueDoubleOperatorConversion()
+    {
+      super(FUNCTION, ColumnType.DOUBLE);
     }
   }
 
-  // calcite converts JSON_VALUE to JSON_VALUE_ANY so we have to wire that up 
too...
-  public static class JsonValueAnyOperatorConversion extends 
AliasedOperatorConversion
+  public static class JsonValueStringOperatorConversion extends 
JsonValueReturningTypeOperatorConversion
   {
-    private static final String FUNCTION_NAME = 
StringUtils.toUpperCase("json_value_any");
+    private static final SqlFunction FUNCTION = 
buildFunction("JSON_VALUE_VARCHAR", SqlTypeName.VARCHAR);
 
-    public JsonValueAnyOperatorConversion()
+    public JsonValueStringOperatorConversion()
     {
-      super(new JsonValueOperatorConversion(), FUNCTION_NAME);
+      super(FUNCTION, ColumnType.STRING);
     }
+  }
+
+  /**
+   * Calcites {@link org.apache.calcite.sql2rel.StandardConvertletTable} 
translates JSON_VALUE
+   */
+  public static class JsonValueAnyOperatorConversion implements 
SqlOperatorConversion
+  {
+    private static final SqlFunction FUNCTION =
+        OperatorConversions.operatorBuilder("JSON_VALUE_ANY")
+                           .operandTypeChecker(
+                               OperandTypes.or(
+                                   OperandTypes.sequence(
+                                       "(expr,path)",
+                                       OperandTypes.family(SqlTypeFamily.ANY),
+                                       
OperandTypes.family(SqlTypeFamily.STRING)
+                                   ),
+                                   OperandTypes.family(
+                                       SqlTypeFamily.ANY,
+                                       SqlTypeFamily.CHARACTER,
+                                       SqlTypeFamily.ANY,
+                                       SqlTypeFamily.ANY,
+                                       SqlTypeFamily.ANY,
+                                       SqlTypeFamily.ANY,
+                                       SqlTypeFamily.ANY
+                                   )
+                               )
+                           )
+                           .operandTypeInference((callBinding, returnType, 
operandTypes) -> {
+                             RelDataTypeFactory typeFactory = 
callBinding.getTypeFactory();
+                             if (operandTypes.length > 5) {
+                               operandTypes[3] = 
typeFactory.createSqlType(SqlTypeName.ANY);
+                               operandTypes[5] = 
typeFactory.createSqlType(SqlTypeName.ANY);
+                             }
+                           })
+                           .returnTypeInference(
+                               ReturnTypes.cascade(
+                                   opBinding -> 
opBinding.getTypeFactory().createTypeWithNullability(
+                                       // STRING is the closest thing we have 
to an ANY type
+                                       // however, this should really be using 
SqlTypeName.ANY.. someday
+                                       
opBinding.getTypeFactory().createSqlType(SqlTypeName.VARCHAR),
+                                       true
+                                   ),
+                                   SqlTypeTransforms.FORCE_NULLABLE
+                               )
+                           )
+                           .functionCategory(SqlFunctionCategory.SYSTEM)
+                           .build();
 
     @Override
     public SqlOperator calciteOperator()
     {
-      return SqlStdOperatorTable.JSON_VALUE_ANY;
+      return FUNCTION;
+    }
+
+    @Nullable
+    @Override
+    public DruidExpression toDruidExpression(
+        PlannerContext plannerContext,
+        RowSignature rowSignature,
+        RexNode rexNode
+    )
+    {
+      final RexCall call = (RexCall) rexNode;
+
+      // calcite parser can allow for a bunch of junk in here that we don't 
care about right now, so the call looks

Review Comment:
   Some of the extra parameters look like things that are meant to alter the 
behavior of the function. (like controlling what the error behavior is.) If we 
don't support them, better to throw an error than silently ignore them. Is this 
feasible?



##########
sql/src/main/java/org/apache/druid/sql/calcite/planner/convertlet/DruidConvertletTable.java:
##########
@@ -44,6 +45,7 @@ public class DruidConvertletTable implements 
SqlRexConvertletTable
       ImmutableList.<DruidConvertletFactory>builder()
                    .add(CurrentTimestampAndFriendsConvertletFactory.INSTANCE)
                    .add(TimeInIntervalConvertletFactory.INSTANCE)
+                   .add(new 
NestedDataOperatorConversions.DruidJsonValueConvertletFactory())

Review Comment:
   Any reason this isn't in a static field like the other ones?



##########
processing/src/main/java/org/apache/druid/query/expression/NestedDataExpressions.java:
##########
@@ -442,38 +442,76 @@ public String name()
     public Expr apply(List<Expr> args)
     {
       final List<NestedPathPart> parts = 
getArg1JsonPathPartsFromLiteral(name(), args);
-      class JsonValueExpr extends ExprMacroTable.BaseScalarMacroFunctionExpr
-      {
-        public JsonValueExpr(List<Expr> args)
-        {
-          super(name(), args);
+      if (args.size() == 3 && args.get(2).isLiteral()) {
+        final ExpressionType castTo = ExpressionType.fromString((String) 
args.get(2).getLiteralValue());
+        if (castTo == null) {
+          throw new IAE("Invalid output type: [%s]", 
args.get(2).getLiteralValue());
         }
-
-        @Override
-        public ExprEval eval(ObjectBinding bindings)
+        class CastJsonValueExpr extends 
ExprMacroTable.BaseScalarMacroFunctionExpr
         {
-          ExprEval input = args.get(0).eval(bindings);
-          return ExprEval.bestEffortOf(
-              NestedPathFinder.findLiteral(unwrap(input), parts)
-          );
-        }
+          public CastJsonValueExpr(List<Expr> args)
+          {
+            super(name(), args);
+          }
 
-        @Override
-        public Expr visit(Shuttle shuttle)
-        {
-          List<Expr> newArgs = args.stream().map(x -> 
x.visit(shuttle)).collect(Collectors.toList());
-          return shuttle.visit(new JsonValueExpr(newArgs));
-        }
+          @Override
+          public ExprEval eval(ObjectBinding bindings)
+          {
+            ExprEval input = args.get(0).eval(bindings);
+            return ExprEval.bestEffortOf(
+                NestedPathFinder.findLiteral(unwrap(input), parts)
+            ).castTo(castTo);
+          }
 
-        @Nullable
-        @Override
-        public ExpressionType getOutputType(InputBindingInspector inspector)
+          @Override
+          public Expr visit(Shuttle shuttle)
+          {
+            List<Expr> newArgs = args.stream().map(x -> 
x.visit(shuttle)).collect(Collectors.toList());
+            return shuttle.visit(new CastJsonValueExpr(newArgs));
+          }
+
+          @Nullable
+          @Override
+          public ExpressionType getOutputType(InputBindingInspector inspector)
+          {
+            return castTo;
+          }
+        }
+        return new CastJsonValueExpr(args);
+      } else {
+        class JsonValueExpr extends ExprMacroTable.BaseScalarMacroFunctionExpr
         {
-          // we cannot infer the output type (well we could say it is 'STRING' 
right now because is all we support...
-          return null;
+          public JsonValueExpr(List<Expr> args)
+          {
+            super(name(), args);
+          }
+
+          @Override
+          public ExprEval eval(ObjectBinding bindings)
+          {
+            ExprEval input = args.get(0).eval(bindings);
+            return ExprEval.bestEffortOf(
+                NestedPathFinder.findLiteral(unwrap(input), parts)
+            );
+          }
+
+          @Override
+          public Expr visit(Shuttle shuttle)
+          {
+            List<Expr> newArgs = args.stream().map(x -> 
x.visit(shuttle)).collect(Collectors.toList());
+            return shuttle.visit(new JsonValueExpr(newArgs));
+          }
+
+          @Nullable
+          @Override
+          public ExpressionType getOutputType(InputBindingInspector inspector)
+          {
+            // we cannot infer the output type (well we could say it is 
'STRING' right now because is all we support...

Review Comment:
   Then why not return STRING?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to