Expressions: Add ExprMacros.#4365
Conversation
… but can convert themselves to any kind of Expr at parse-time. ExprMacroTable is an extension point for adding new ExprMacros. Anything that might need to parse expressions needs an ExprMacroTable, which can be injected through Guice.
|
Broken out from #4360. |
|
👍 |
| protected final long evalLong(long left, long right) | ||
| { | ||
| return LongMath.pow(left, (int)right); | ||
| return LongMath.pow(left, (int) right); |
There was a problem hiding this comment.
Maybe Ints.checkedCast() for sanity
| // Built-in functions. | ||
| final Function function = Parser.getFunction(fnName); | ||
| if (function == null) { | ||
| throw new RuntimeException("function '" + fnName + "' is not defined."); |
There was a problem hiding this comment.
Could use Druid's RE with pattern.
|
|
||
| public class ExprMacroTable | ||
| { | ||
| private static final ExprMacroTable NIL = new ExprMacroTable(Collections.emptyList()); |
There was a problem hiding this comment.
Why "nil"? Looks more like "empty"
There was a problem hiding this comment.
I named it nil similar to how the empty list is named nil in Lisp and in Scala, in honor of the macro systems in those languages.
| { | ||
| final ExprMacro exprMacro = macroMap.get(functionName.toLowerCase()); | ||
| if (exprMacro == null) { | ||
| return null; |
There was a problem hiding this comment.
Why silently consume the situation when the function is not found? Maybe throw exception?
There was a problem hiding this comment.
In ExprListenerImpl, the macro table is checked first, then built-in functions. null return makes this pattern simpler; if this threw an exception we'd have to catch it and ignore it.
There was a problem hiding this comment.
I annotated this with @Nullable and wrote a javadoc.
| public Expr apply(final List<Expr> args) | ||
| { | ||
| if (args.size() < 2 || args.size() > 3) { | ||
| throw new IAE("'%s' must have 2 or 3 arguments", name()); |
There was a problem hiding this comment.
Changed to "Function[%s] must have 2 or 3 arguments"
| if (escape != null && escape.length() != 1) { | ||
| throw new IllegalArgumentException("Escape must be null or a single character"); | ||
| } else { | ||
| escapeChar = (escape == null || escape.isEmpty()) ? null : escape.charAt(0); |
There was a problem hiding this comment.
isEmpty() check is redundant because length must be 1, it is checked above.
There was a problem hiding this comment.
Removed the redundant check.
|
The transient integration test failure is: #4359 (comment) |
|
@leventov thanks for review; pushed changes. |
| * @throws IllegalStateException if expr is not a literal | ||
| */ | ||
| @Nullable | ||
| default Object getLiteralValue() |
There was a problem hiding this comment.
How about adding LiteralExpr extending Expr and having this method? If so, other types of exprs don't have to have this method.
There was a problem hiding this comment.
I wanted to avoid having instanceof checks and casts all over the place.
| } | ||
|
|
||
| public static boolean hasFunction(String name) | ||
| public static Function getFunction(String name) |
There was a problem hiding this comment.
We need to check argument types maybe here for type safety in the future? I recognize nulls are currently returned for unsupported argument types, but if we're going to make Druid more SQL-compatible, we need to check types somewhere.
There was a problem hiding this comment.
Yes, I think at some point, the expressions should be type-aware at parse time. It will help with performance too. I think in the future, we want to be doing code generation and that will require advance knowledge of what type everything is going to be.
For now, they aren't type-aware until runtime.
| return fieldName != null ? Collections.singletonList(fieldName) : Parser.findRequiredBindings(expression); | ||
| return fieldName != null | ||
| ? Collections.singletonList(fieldName) | ||
| : Parser.findRequiredBindings(Parser.parse(expression, macroTable)); |
There was a problem hiding this comment.
It looks to be possible to keep parse result if fieldName is null in the constructor. Same for other AggregatorFactorys.
There was a problem hiding this comment.
It's true, but would require some refactoring of AggregatorUtil and its callers. I think it'd be better to do it in a separate patch.
|
@gianm thanks for breaking the patch into small ones. Looks good to me overall. |
|
Thanks for the review @jihoonson. |
ExprMacros have the same syntax as functions but can convert themselves
to any kind of Expr at parse-time.
ExprMacroTable is an extension point for adding new ExprMacros. Anything
that might need to parse expressions needs an ExprMacroTable, which can
be injected through Guice.
The motivation here is two fold.
likefunction added here is an example: its pattern is compiled into a LikeMatcher object at expression-parse-time.