Skip to content

Expressions: Add ExprMacros.#4365

Merged
gianm merged 3 commits intoapache:masterfrom
gianm:se-expr-macros
Jun 8, 2017
Merged

Expressions: Add ExprMacros.#4365
gianm merged 3 commits intoapache:masterfrom
gianm:se-expr-macros

Conversation

@gianm
Copy link
Copy Markdown
Contributor

@gianm gianm commented Jun 5, 2017

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.

  1. Provide a way to define functions where, for efficiency, some parts of the evaluation should be done ahead of time. The like function added here is an example: its pattern is compiled into a LikeMatcher object at expression-parse-time.
  2. Provide a way for extensions to add new functions (they can do it through defining a macro).

… 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.
@gianm gianm added this to the 0.10.2 milestone Jun 5, 2017
@gianm
Copy link
Copy Markdown
Contributor Author

gianm commented Jun 5, 2017

Broken out from #4360.

@gianm gianm changed the title Expressions: Add ExprMacros, which have the same syntax as functions, Expressions: Add ExprMacros. Jun 5, 2017
@gianm gianm removed this from the 0.10.2 milestone Jun 6, 2017
@fjy
Copy link
Copy Markdown
Contributor

fjy commented Jun 6, 2017

👍

protected final long evalLong(long left, long right)
{
return LongMath.pow(left, (int)right);
return LongMath.pow(left, (int) right);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe Ints.checkedCast() for sanity

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed.

// Built-in functions.
final Function function = Parser.getFunction(fnName);
if (function == null) {
throw new RuntimeException("function '" + fnName + "' is not defined.");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could use Druid's RE with pattern.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed.


public class ExprMacroTable
{
private static final ExprMacroTable NIL = new ExprMacroTable(Collections.emptyList());
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why "nil"? Looks more like "empty"

Copy link
Copy Markdown
Contributor Author

@gianm gianm Jun 7, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why silently consume the situation when the function is not found? Maybe throw exception?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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());
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[%s] is usually used

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isEmpty() check is redundant because length must be 1, it is checked above.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the redundant check.

@leventov
Copy link
Copy Markdown
Member

leventov commented Jun 6, 2017

The transient integration test failure is: #4359 (comment)

@gianm
Copy link
Copy Markdown
Contributor Author

gianm commented Jun 7, 2017

@leventov thanks for review; pushed changes.

* @throws IllegalStateException if expr is not a literal
*/
@Nullable
default Object getLiteralValue()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about adding LiteralExpr extending Expr and having this method? If so, other types of exprs don't have to have this method.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to avoid having instanceof checks and casts all over the place.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok.

}

public static boolean hasFunction(String name)
public static Function getFunction(String name)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

@gianm gianm Jun 7, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. It sounds good.

return fieldName != null ? Collections.singletonList(fieldName) : Parser.findRequiredBindings(expression);
return fieldName != null
? Collections.singletonList(fieldName)
: Parser.findRequiredBindings(Parser.parse(expression, macroTable));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks to be possible to keep parse result if fieldName is null in the constructor. Same for other AggregatorFactorys.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok.

@jihoonson
Copy link
Copy Markdown
Contributor

@gianm thanks for breaking the patch into small ones. Looks good to me overall.

@gianm
Copy link
Copy Markdown
Contributor Author

gianm commented Jun 7, 2017

Thanks for the review @jihoonson.

Copy link
Copy Markdown
Member

@leventov leventov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@gianm gianm added this to the 0.10.1 milestone Jun 8, 2017
@gianm gianm merged commit 1f2afcc into apache:master Jun 8, 2017
@gianm gianm deleted the se-expr-macros branch June 8, 2017 13:32
@gianm gianm mentioned this pull request Jul 31, 2019
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants