Repository navigation
fix(clp-s): Handle pure wildcards and unexpected literal types correctly in EvaluateTimestampIndex (fixes #1096).
#1277
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
Changes from 8 commits
6b0d3af
ba7d320
8056b96
207b10d
39d3e63
bac07e5
00d5fff
d957968
0467291
f33b147
e897bf1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -3,13 +3,15 @@ | |||||||||||||||||||||||||||||||||||||||||||||||
| #include "ast/AndExpr.hpp" | ||||||||||||||||||||||||||||||||||||||||||||||||
| #include "ast/Expression.hpp" | ||||||||||||||||||||||||||||||||||||||||||||||||
| #include "ast/FilterExpr.hpp" | ||||||||||||||||||||||||||||||||||||||||||||||||
| #include "ast/FilterOperation.hpp" | ||||||||||||||||||||||||||||||||||||||||||||||||
| #include "ast/Integral.hpp" | ||||||||||||||||||||||||||||||||||||||||||||||||
| #include "ast/Literal.hpp" | ||||||||||||||||||||||||||||||||||||||||||||||||
| #include "ast/OrExpr.hpp" | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| using clp_s::search::ast::AndExpr; | ||||||||||||||||||||||||||||||||||||||||||||||||
| using clp_s::search::ast::Expression; | ||||||||||||||||||||||||||||||||||||||||||||||||
| using clp_s::search::ast::FilterExpr; | ||||||||||||||||||||||||||||||||||||||||||||||||
| using clp_s::search::ast::FilterOperation; | ||||||||||||||||||||||||||||||||||||||||||||||||
| using clp_s::search::ast::Integral; | ||||||||||||||||||||||||||||||||||||||||||||||||
| using clp_s::search::ast::Integral64; | ||||||||||||||||||||||||||||||||||||||||||||||||
| using clp_s::search::ast::literal_type_bitmask_t; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -63,16 +65,19 @@ EvaluatedValue EvaluateTimestampIndex::run(std::shared_ptr<Expression> const& ex | |||||||||||||||||||||||||||||||||||||||||||||||
| range_it != m_timestamp_dict->tokenized_column_to_range_end(); | ||||||||||||||||||||||||||||||||||||||||||||||||
| range_it++) | ||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||
| // Don't attempt to evaluate the timestamp index against columns with wildcard tokens. | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (column->is_unresolved_descriptor()) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| continue; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| std::vector<std::string> const& tokens = range_it->first; | ||||||||||||||||||||||||||||||||||||||||||||||||
| auto const& descriptors = column->get_descriptor_list(); | ||||||||||||||||||||||||||||||||||||||||||||||||
| // TODO: handle wildcard matching; the initial check on timestamp index happens | ||||||||||||||||||||||||||||||||||||||||||||||||
| // before schema matching, so | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (tokens.size() != descriptors.size()) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| continue; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| bool matched = true; | ||||||||||||||||||||||||||||||||||||||||||||||||
| for (size_t i = 0; i < descriptors.size(); ++i) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| for (size_t i{0ULL}; i < descriptors.size(); ++i) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (tokens[i] != descriptors[i].get_token()) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| matched = false; | ||||||||||||||||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -82,20 +87,40 @@ EvaluatedValue EvaluateTimestampIndex::run(std::shared_ptr<Expression> const& ex | |||||||||||||||||||||||||||||||||||||||||||||||
| continue; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| EvaluatedValue ret; | ||||||||||||||||||||||||||||||||||||||||||||||||
| // this is safe after type narrowing because all DateType literals are either | ||||||||||||||||||||||||||||||||||||||||||||||||
| // Integral or a derived class of Integral | ||||||||||||||||||||||||||||||||||||||||||||||||
| Integral64 literal = std::static_pointer_cast<Integral>(filter->get_operand())->get(); | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (std::holds_alternative<int64_t>(literal)) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| ret = range_it->second->evaluate_filter( | ||||||||||||||||||||||||||||||||||||||||||||||||
| filter->get_operation(), | ||||||||||||||||||||||||||||||||||||||||||||||||
| std::get<int64_t>(literal) | ||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||
| // Leave handling EXISTS/NEXISTS to schema matching. | ||||||||||||||||||||||||||||||||||||||||||||||||
| auto const filter_op{filter->get_operation()}; | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (FilterOperation::EXISTS == filter_op || FilterOperation::NEXISTS == filter_op) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| return EvaluatedValue::Unknown; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correct me if I'm wrong.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is just evaluating against the timestamp index -- it won't tell us whether the timestamp exists on a given record/schema, so we leave that up to the schema matching pass.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Quick question: what happens if an archive contains some messages without timestamp fields? I think we simply skip recording timestamps for those messages? But if a query includes timestamp filters and none of the timestamped messages in that archive match (out of range), should we still scan the remaining messages that don’t have timestamp fields?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah we currently don't record any timestamps and will skip them in a scan that contains a timestamp filter. It probably makes sense to add some options for users to get around this (e.g. assign timestamp based on surrounding log events at ingestion time, or potentially still search log events missing timestamps if the timestamp range index matches), but that's future work. |
||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive) Hoist EXISTS/NEXISTS guard before the range loop. This result is invariant of the timestamp dictionary; return Unknown earlier to skip unnecessary work. Apply: @@
- for (auto range_it = m_timestamp_dict->tokenized_column_to_range_begin();
+ // Leave handling EXISTS/NEXISTS to schema matching.
+ auto const filter_op{filter->get_operation()};
+ if (FilterOperation::EXISTS == filter_op || FilterOperation::NEXISTS == filter_op) {
+ return EvaluatedValue::Unknown;
+ }
+
+ for (auto range_it = m_timestamp_dict->tokenized_column_to_range_begin();
range_it != m_timestamp_dict->tokenized_column_to_range_end();
range_it++)
{
@@
- // Leave handling EXISTS/NEXISTS to schema matching.
- auto const filter_op{filter->get_operation()};
- if (FilterOperation::EXISTS == filter_op || FilterOperation::NEXISTS == filter_op) {
- return EvaluatedValue::Unknown;
- }📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| auto const literal{filter->get_operand()}; | ||||||||||||||||||||||||||||||||||||||||||||||||
| // Defend against future FilterOperation types that don't take a literal. | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (nullptr == literal) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| return EvaluatedValue::Unknown; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive) Hoist null-literal guard before the range loop as well. Like the EXISTS/NEXISTS case, this is dictionary-independent. Apply: @@
- for (auto range_it = m_timestamp_dict->tokenized_column_to_range_begin();
+ auto const literal{filter->get_operand()};
+ // Defend against FilterOperation types that don't take a literal.
+ if (nullptr == literal) {
+ return EvaluatedValue::Unknown;
+ }
+
+ for (auto range_it = m_timestamp_dict->tokenized_column_to_range_begin();
range_it != m_timestamp_dict->tokenized_column_to_range_end();
range_it++)
{
@@
- auto const literal{filter->get_operand()};
- // Defend against future FilterOperation types that don't take a literal.
- if (nullptr == literal) {
- return EvaluatedValue::Unknown;
- }📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| // Handle the case where the literal is a pure wildcard. | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (literal->as_any(filter_op)) { | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And do we have still have an
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't if we run this after the convert to exists pass. Thinking about this more though we should also return Since that's the case I think I can just delete this code block and rely on following through to the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually I can probably also delete the special case dedicated to exists/nexists and rely on the check for whether the filter has a literal -- going to do a pass and get rid of redundant special cases.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, that's cool |
||||||||||||||||||||||||||||||||||||||||||||||||
| switch (filter_op) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| case FilterOperation::EQ: | ||||||||||||||||||||||||||||||||||||||||||||||||
| return filter->is_inverted() ? EvaluatedValue::False : EvaluatedValue::True; | ||||||||||||||||||||||||||||||||||||||||||||||||
| case FilterOperation::NEQ: | ||||||||||||||||||||||||||||||||||||||||||||||||
| return filter->is_inverted() ? EvaluatedValue::True : EvaluatedValue::False; | ||||||||||||||||||||||||||||||||||||||||||||||||
| default: | ||||||||||||||||||||||||||||||||||||||||||||||||
| // Not reachable. | ||||||||||||||||||||||||||||||||||||||||||||||||
| return EvaluatedValue::Unknown; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
coderabbitai[bot] marked this conversation as resolved.
Resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| EvaluatedValue ret{EvaluatedValue::Unknown}; | ||||||||||||||||||||||||||||||||||||||||||||||||
| int64_t int_literal{}; | ||||||||||||||||||||||||||||||||||||||||||||||||
| double float_literal{}; | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (literal->as_int(int_literal, filter_op)) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| ret = range_it->second->evaluate_filter(filter_op, int_literal); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } else if (literal->as_float(float_literal, filter_op)) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| ret = range_it->second->evaluate_filter(filter_op, float_literal); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||
| ret = range_it->second->evaluate_filter( | ||||||||||||||||||||||||||||||||||||||||||||||||
| filter->get_operation(), | ||||||||||||||||||||||||||||||||||||||||||||||||
| std::get<double>(literal) | ||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||
| return EvaluatedValue::Unknown; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| if (ret == EvaluatedValue::True) { | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.