Skip to content

EQL: Add optional fields #78769

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed

Conversation

astefan
Copy link
Contributor

@astefan astefan commented Oct 6, 2021

Add optional fields, with usage limited to queries only, excluding optional fields as joining fields.
Optional fields have a question mark in front of their name (?some_field) and can be used in standalone event queries and sequences, but not as join fields.
If the field exists in at least one index that's getting queried, that field will actually be used in queries.
If the field doesn't exist in any of the indices, all its mentions in query will be replaced with null.

@astefan astefan requested review from costin, Luegg and bpintea October 6, 2021 14:51
@elasticmachine elasticmachine added the Team:QL (Deprecated) Meta label for query languages team label Oct 6, 2021
@elasticmachine
Copy link
Collaborator

Pinging @elastic/es-ql (Team:QL)

Copy link
Member

@costin costin left a comment

Choose a reason for hiding this comment

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

Besides the various code comments, I'd like to see more tests for fields that have ? in their name and combine that with optional fields:

by `?somefield`
by ?`?somefield`
where `some?field` == null
where ?`some?field` == null
where `somefield?`
where ?`somefield?`

[query] by `?somefield` // should work
[query] by ?`?somefield` // should throw parsing error

@@ -20,6 +20,13 @@
"path": "sequence"
}
}
},
"no_docs_field": {
Copy link
Member

Choose a reason for hiding this comment

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

Improved name would be - "optional_field_mapping_only". Potentially drop optional.

"no_docs_field": {
"type": "keyword"
},
"default_null_value_field": {
Copy link
Member

Choose a reason for hiding this comment

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

"optional_field_default_null"

Expression named = resolveAgainstList(u, childrenOutput);
// if it's not resolved (it doesn't exist in mappings) and it's an optional field, replace it with "null"
if (named == null && optionals.contains(u)) {
named = Literal.NULL;
Copy link
Member

Choose a reason for hiding this comment

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

you could preserve the source (for better error messaging):

new Literal(named.source(), null, DataTypes.NULL)

public class AstBuilder extends LogicalPlanBuilder {

AstBuilder(ParserParams params) {
Copy link
Member

Choose a reason for hiding this comment

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

You could keep the old constructor around and make it a shortcut to this(params, emptySet())

return ctx != null ? visitList(this, ctx.expression(), Attribute.class) : emptyList();
List<ExpressionContext> keys = ctx.expression();
for (ExpressionContext k : keys) {
if ("?".equals(k.getStart().getText())) {
Copy link
Member

Choose a reason for hiding this comment

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

I don't think this is correct - the check needs to happen on OPTIONAL not the ? string since it might get confused by a quoted identifier starting with ?, e.g.:

[queryA] by `?specialField`

return new UnresolvedAttribute(source(ctx), visitQualifiedName(ctx.qualifiedName()));
String name = visitQualifiedName(ctx.qualifiedName());
UnresolvedAttribute attr = new UnresolvedAttribute(source(ctx), name);
if ("?".equals(ctx.getStart().getText())) {
Copy link
Member

Choose a reason for hiding this comment

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

Same as above - the context is too low-level.

@@ -41,6 +45,8 @@
private final Planner planner;
private final CircuitBreaker circuitBreaker;

private final Set<UnresolvedAttribute> optionals = new HashSet<>();
Copy link
Member

Choose a reason for hiding this comment

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

Instead of passing this per session, it should be read from the parser.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@costin Actually, this is the place where the list of optionals is "initialized" and then passed on to all necessary instances so that, in the end, gets populated in the expression builder. Later on, in the Analyzer, this list is checked in ResolveRefs.


public EqlParser(Set<UnresolvedAttribute> optionals) {
Copy link
Member

Choose a reason for hiding this comment

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

The set shouldn't be set to the constructor but rather the parser detect all the optional fields and return them accordingly. As it stands the caller is forced to pass an empty set for the parser to initialize it.

Copy link
Contributor

@Luegg Luegg left a comment

Choose a reason for hiding this comment

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

This approach of populating a set of optional attributes is somewhat surprising to me. Why is the optionality not a property of UnresolvedAttribute?

Also, it looks to me that declaring an attribute as optional in one place makes the attribute optional everywhere, e.g. in any where ?foo == 1 or foo == 2. Is that the intended behavior? I could imagine that users might expect an error in this case (or at least if the foo attribute does not exist).

@astefan
Copy link
Contributor Author

astefan commented Oct 22, 2021

Superseded by #79677

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants