Skip to content

Commit f6bbee6

Browse files
[query-engine] Relax restrictions in KQL summarize expressions (open-telemetry#1262)
## Changes * Allow aggregate and group by expression without explicit assignment in KQL summarize expressions ## Details Today you can do: * `summarize by Timestamp = bin(Timestamp, 1m)` * `summarize Count = count()` But you will get an error if you do: * `summarize by bin(Timestamp, 1m)` * `summarize count()` This PR makes the second two examples work. In the first case the name `Timestamp` will be used and in the second case `count`. This is done by giving the KQL parser some logic to determine the identifier for any given scalar expression. Note: We could probably use this in extend\project* cases too but I plan to look at that in a follow-up. Fixes open-telemetry#1243 --------- Co-authored-by: Drew Relmas <[email protected]>
1 parent 36c7123 commit f6bbee6

File tree

8 files changed

+454
-192
lines changed

8 files changed

+454
-192
lines changed

rust/experimental/query_engine/engine-recordset/src/summary/summary_data_expression.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ pub fn execute_summary_data_expression<'a, TRecord: Record>(
6161
None => {
6262
execution_context.add_diagnostic_if_enabled(
6363
RecordSetEngineDiagnosticLevel::Warn,
64-
expression.get_value_expression().as_ref().unwrap(),
64+
expression.get_value_expression().unwrap(),
6565
|| {
6666
format!(
6767
"Value expression value of '{:?}' type could not be converted to integer or double",
@@ -104,7 +104,7 @@ pub fn execute_summary_data_expression<'a, TRecord: Record>(
104104
None => {
105105
execution_context.add_diagnostic_if_enabled(
106106
RecordSetEngineDiagnosticLevel::Warn,
107-
expression.get_value_expression().as_ref().unwrap(),
107+
expression.get_value_expression().unwrap(),
108108
|| {
109109
format!(
110110
"Value expression value of '{:?}' type could not be converted to integer or double",

rust/experimental/query_engine/expressions/src/summary/summary_data_expression.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,8 @@ impl AggregationExpression {
108108
self.aggregation_function.clone()
109109
}
110110

111-
pub fn get_value_expression(&self) -> &Option<ScalarExpression> {
112-
&self.value_expression
111+
pub fn get_value_expression(&self) -> Option<&ScalarExpression> {
112+
self.value_expression.as_ref()
113113
}
114114

115115
pub(crate) fn try_fold(

rust/experimental/query_engine/kql-parser/src/aggregate_expressions.rs

Lines changed: 67 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,67 +5,107 @@ use data_engine_expressions::*;
55
use data_engine_parser_abstractions::*;
66
use pest::iterators::Pair;
77

8-
use crate::{Rule, scalar_expression::parse_scalar_expression};
8+
use crate::{
9+
Rule,
10+
scalar_expression::{parse_scalar_expression, try_resolve_identifier},
11+
};
912

10-
pub(crate) fn parse_aggregate_assignment_expression(
11-
aggregate_assignment_expression_rule: Pair<Rule>,
13+
pub(crate) fn parse_aggregate_expression(
14+
aggregate_expression_rule: Pair<Rule>,
1215
scope: &dyn ParserScope,
1316
) -> Result<(Box<str>, AggregationExpression), ParserError> {
14-
let mut aggregate_assignment_rules = aggregate_assignment_expression_rule.into_inner();
17+
let mut aggregate_expression_rules = aggregate_expression_rule.into_inner();
18+
19+
let first_rule = aggregate_expression_rules.next().unwrap();
20+
let first_rule_location = to_query_location(&first_rule);
1521

16-
let destination_rule = aggregate_assignment_rules.next().unwrap();
17-
let destination_rule_location = to_query_location(&destination_rule);
22+
match first_rule.as_rule() {
23+
Rule::identifier_literal => {
24+
let identifier = crate::tabular_expressions::validate_summary_identifier(
25+
scope,
26+
first_rule_location,
27+
&[first_rule.as_str().trim().into()],
28+
)?;
1829

19-
let identifier = destination_rule.as_str().trim();
30+
let aggregation_expression =
31+
parse_aggregate_expressions(aggregate_expression_rules.next().unwrap(), scope)?;
2032

21-
crate::tabular_expressions::validate_summary_identifier(
22-
scope,
23-
destination_rule_location,
24-
identifier,
25-
)?;
33+
Ok((identifier.into(), aggregation_expression))
34+
}
35+
_ => {
36+
let aggregation_expression = parse_aggregate_expressions(first_rule, scope)?;
2637

27-
let aggregation_expression =
28-
parse_aggregate_expression(aggregate_assignment_rules.next().unwrap(), scope)?;
38+
let identifier = resolve_identifier(&aggregation_expression, scope)?;
2939

30-
Ok((identifier.into(), aggregation_expression))
40+
let full_identifier = crate::tabular_expressions::validate_summary_identifier(
41+
scope,
42+
first_rule_location,
43+
&identifier,
44+
)?;
45+
46+
Ok((full_identifier.into(), aggregation_expression))
47+
}
48+
}
3149
}
3250

33-
fn parse_aggregate_expression(
34-
aggregate_expression_rule: Pair<Rule>,
51+
fn parse_aggregate_expressions(
52+
aggregate_expressions_rule: Pair<Rule>,
3553
scope: &dyn ParserScope,
3654
) -> Result<AggregationExpression, ParserError> {
37-
let query_location = to_query_location(&aggregate_expression_rule);
55+
let query_location = to_query_location(&aggregate_expressions_rule);
3856

39-
let aggregate = match aggregate_expression_rule.as_rule() {
57+
let aggregate = match aggregate_expressions_rule.as_rule() {
4058
Rule::average_aggregate_expression => AggregationExpression::new(
4159
query_location,
4260
AggregationFunction::Average,
43-
Some(parse_scalar_expression(aggregate_expression_rule, scope)?),
61+
Some(parse_scalar_expression(aggregate_expressions_rule, scope)?),
4462
),
4563
Rule::count_aggregate_expression => {
4664
AggregationExpression::new(query_location, AggregationFunction::Count, None)
4765
}
4866
Rule::maximum_aggregate_expression => AggregationExpression::new(
4967
query_location,
5068
AggregationFunction::Maximum,
51-
Some(parse_scalar_expression(aggregate_expression_rule, scope)?),
69+
Some(parse_scalar_expression(aggregate_expressions_rule, scope)?),
5270
),
5371
Rule::minimum_aggregate_expression => AggregationExpression::new(
5472
query_location,
5573
AggregationFunction::Minimum,
56-
Some(parse_scalar_expression(aggregate_expression_rule, scope)?),
74+
Some(parse_scalar_expression(aggregate_expressions_rule, scope)?),
5775
),
5876
Rule::sum_aggregate_expression => AggregationExpression::new(
5977
query_location,
6078
AggregationFunction::Sum,
61-
Some(parse_scalar_expression(aggregate_expression_rule, scope)?),
79+
Some(parse_scalar_expression(aggregate_expressions_rule, scope)?),
6280
),
63-
_ => panic!("Unexpected rule in aggregate_expression: {aggregate_expression_rule}"),
81+
_ => panic!("Unexpected rule in aggregate_expression: {aggregate_expressions_rule}"),
6482
};
6583

6684
Ok(aggregate)
6785
}
6886

87+
fn resolve_identifier(
88+
aggregation_expression: &AggregationExpression,
89+
scope: &dyn ParserScope,
90+
) -> Result<Vec<Box<str>>, ParserError> {
91+
let f = match aggregation_expression.get_aggregation_function() {
92+
AggregationFunction::Average => "avg",
93+
AggregationFunction::Count => "count",
94+
AggregationFunction::Maximum => "max",
95+
AggregationFunction::Minimum => "min",
96+
AggregationFunction::Sum => "sum",
97+
};
98+
99+
if let Some(s) = &aggregation_expression.get_value_expression()
100+
&& let Some(mut i) = try_resolve_identifier(s, scope)?
101+
{
102+
i.insert(0, f.into());
103+
return Ok(i);
104+
}
105+
106+
Ok(vec![f.into()])
107+
}
108+
69109
#[cfg(test)]
70110
mod tests {
71111
use pest::Parser;
@@ -81,11 +121,9 @@ mod tests {
81121

82122
let state = ParserState::new(input);
83123

84-
let mut result =
85-
KqlPestParser::parse(Rule::aggregate_assignment_expression, input).unwrap();
124+
let mut result = KqlPestParser::parse(Rule::aggregate_expression, input).unwrap();
86125

87-
let expression =
88-
parse_aggregate_assignment_expression(result.next().unwrap(), &state).unwrap();
126+
let expression = parse_aggregate_expression(result.next().unwrap(), &state).unwrap();
89127

90128
assert_eq!(expected, expression);
91129
};
@@ -114,7 +152,7 @@ mod tests {
114152

115153
let expression = parse_aggregate_expression(result.next().unwrap(), &state).unwrap();
116154

117-
assert_eq!(expected, expression);
155+
assert_eq!(expected, expression.1);
118156
};
119157

120158
run_test_success(

rust/experimental/query_engine/kql-parser/src/kql.pest

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -284,26 +284,27 @@ sum_aggregate_expression = {
284284
"sum" ~ "(" ~ scalar_expression ~ ")"
285285
}
286286

287-
aggregate_expression = _{
287+
aggregate_expressions = _{
288288
average_aggregate_expression
289289
| count_aggregate_expression
290290
| maximum_aggregate_expression
291291
| minimum_aggregate_expression
292292
| sum_aggregate_expression
293293
}
294294

295-
aggregate_assignment_expression = {
296-
identifier_literal ~ "=" ~ aggregate_expression
295+
aggregate_expression = {
296+
identifier_literal ~ "=" ~ aggregate_expressions
297+
| aggregate_expressions
297298
}
298299

299300
group_by_expression = {
300301
identifier_literal ~ "=" ~ scalar_expression
301-
| accessor_expression
302+
| scalar_expression
302303
}
303304

304305
summarize_expression = {
305306
"summarize"
306-
~ (aggregate_assignment_expression ~ ("," ~ aggregate_assignment_expression)*)?
307+
~ (aggregate_expression ~ ("," ~ aggregate_expression)*)?
307308
~ ("by" ~ group_by_expression ~ ("," ~ group_by_expression)*)?
308309
~ ("|" ~ tabular_expressions)*
309310
}

rust/experimental/query_engine/kql-parser/src/scalar_expression.rs

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,143 @@ pub(crate) fn parse_scalar_unary_expression(
310310
})
311311
}
312312

313+
pub(crate) fn try_resolve_identifier(
314+
scalar_expression: &ScalarExpression,
315+
scope: &dyn ParserScope,
316+
) -> Result<Option<Vec<Box<str>>>, ParserError> {
317+
let r = match scalar_expression {
318+
ScalarExpression::Attached(a) => {
319+
parse_identifier_from_accessor(a.get_name().get_value(), a.get_value_accessor(), scope)
320+
}
321+
ScalarExpression::Constant(c) => {
322+
let name = scope
323+
.get_constant_name(c.get_constant_id())
324+
.expect("Constant not found");
325+
326+
parse_identifier_from_accessor(name, c.get_value_accessor(), scope)
327+
}
328+
ScalarExpression::Source(s) => {
329+
parse_identifier_from_accessor("source", s.get_value_accessor(), scope)
330+
}
331+
ScalarExpression::Variable(v) => {
332+
parse_identifier_from_accessor(v.get_name().get_value(), v.get_value_accessor(), scope)
333+
}
334+
ScalarExpression::Math(m) => match m {
335+
MathScalarExpression::Add(_) => Ok(None),
336+
MathScalarExpression::Bin(b) => try_resolve_identifier(b.get_left_expression(), scope),
337+
MathScalarExpression::Ceiling(u) => {
338+
try_resolve_identifier(u.get_value_expression(), scope)
339+
}
340+
MathScalarExpression::Divide(_) => Ok(None),
341+
MathScalarExpression::Floor(u) => {
342+
try_resolve_identifier(u.get_value_expression(), scope)
343+
}
344+
MathScalarExpression::Modulus(_) => Ok(None),
345+
MathScalarExpression::Multiply(_) => Ok(None),
346+
MathScalarExpression::Negate(_) => Ok(None),
347+
MathScalarExpression::Subtract(_) => Ok(None),
348+
},
349+
ScalarExpression::Convert(c) => match c {
350+
ConvertScalarExpression::Boolean(c) => {
351+
try_resolve_identifier(c.get_inner_expression(), scope)
352+
}
353+
ConvertScalarExpression::DateTime(c) => {
354+
try_resolve_identifier(c.get_inner_expression(), scope)
355+
}
356+
ConvertScalarExpression::Double(c) => {
357+
try_resolve_identifier(c.get_inner_expression(), scope)
358+
}
359+
ConvertScalarExpression::Integer(c) => {
360+
try_resolve_identifier(c.get_inner_expression(), scope)
361+
}
362+
ConvertScalarExpression::String(c) => {
363+
try_resolve_identifier(c.get_inner_expression(), scope)
364+
}
365+
ConvertScalarExpression::TimeSpan(c) => {
366+
try_resolve_identifier(c.get_inner_expression(), scope)
367+
}
368+
},
369+
ScalarExpression::Case(_) => Ok(None),
370+
ScalarExpression::Coalesce(_) => Ok(None),
371+
ScalarExpression::Collection(_) => Ok(None),
372+
ScalarExpression::Conditional(_) => Ok(None),
373+
ScalarExpression::Temporal(_) => Ok(None),
374+
ScalarExpression::Length(l) => {
375+
if let Some(mut i) = try_resolve_identifier(l.get_inner_expression(), scope)? {
376+
i.insert(0, "len".into());
377+
return Ok(Some(i));
378+
}
379+
380+
Ok(None)
381+
}
382+
ScalarExpression::Logical(_) => Ok(None),
383+
ScalarExpression::Parse(_) => Ok(None),
384+
ScalarExpression::Slice(_) => Ok(None),
385+
ScalarExpression::Static(_) => Ok(None),
386+
ScalarExpression::Text(_) => Ok(None),
387+
};
388+
389+
if let Ok(Some(mut identifier)) = r {
390+
// Note: The identifier path may contain source.[default_map_key]. We always
391+
// remove "source" and then remove the default_map_key if the mode is
392+
// enabled.
393+
if let Some("source") = identifier.first().map(|v| v.as_ref()) {
394+
identifier.remove(0);
395+
396+
if let Some(schema) = scope.get_source_schema()
397+
&& let Some((key, _)) = schema.get_default_map()
398+
&& let Some(first) = identifier.first().map(|v| v.as_ref())
399+
&& key == first
400+
{
401+
identifier.remove(0);
402+
}
403+
}
404+
return Ok(Some(identifier));
405+
}
406+
407+
r
408+
}
409+
410+
fn parse_identifier_from_accessor(
411+
root: &str,
412+
value_accessor: &ValueAccessor,
413+
scope: &dyn ParserScope,
414+
) -> Result<Option<Vec<Box<str>>>, ParserError> {
415+
let mut identifier: Vec<Box<str>> = vec![root.into()];
416+
for selector in value_accessor.get_selectors() {
417+
match selector {
418+
ScalarExpression::Static(s) => match s.to_value() {
419+
Value::String(s) => {
420+
identifier.push(s.get_value().into());
421+
}
422+
Value::Integer(i) => {
423+
identifier.push(format!("{}", i.get_value()).into());
424+
}
425+
_ => {
426+
return Ok(None);
427+
}
428+
},
429+
ScalarExpression::Constant(c) => {
430+
let name = scope
431+
.get_constant_name(c.get_constant_id())
432+
.expect("Constant not found");
433+
434+
if let Some(mut i) =
435+
parse_identifier_from_accessor(name, c.get_value_accessor(), scope)?
436+
{
437+
identifier.append(&mut i);
438+
} else {
439+
return Ok(None);
440+
}
441+
}
442+
_ => {
443+
return Ok(None);
444+
}
445+
}
446+
}
447+
Ok(Some(identifier))
448+
}
449+
313450
#[cfg(test)]
314451
mod tests {
315452
use pest::Parser;

rust/experimental/query_engine/kql-parser/src/scalar_primitive_expressions.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -544,7 +544,7 @@ pub(crate) fn parse_accessor_expression(
544544
// source['const_str'] = 1 so the const_str is not evaluated.
545545
if allow_root_scalar {
546546
if let Some((constant_id, value_type)) =
547-
scope.get_constant(root_accessor_identity.get_value())
547+
scope.get_constant_id(root_accessor_identity.get_value())
548548
{
549549
return Ok(ScalarExpression::Constant(
550550
ReferenceConstantScalarExpression::new(

0 commit comments

Comments
 (0)