From b4d234af9c7c122487d8a75ac5d77412d17a1a0e Mon Sep 17 00:00:00 2001 From: Anna Mayzner Date: Thu, 23 Jan 2025 16:49:24 +0000 Subject: [PATCH] tp: StructuredQuery supports IS_NULL and IS_NOT_NULL Change-Id: I18c091d4b6ea5f6a96a661742263f45f104029da --- .../perfetto_sql/structured_query.proto | 9 ++- .../generator/structured_query_generator.cc | 28 +++++--- .../structured_query_generator_unittest.cc | 70 +++++++++++++++++++ 3 files changed, 95 insertions(+), 12 deletions(-) diff --git a/protos/perfetto/perfetto_sql/structured_query.proto b/protos/perfetto/perfetto_sql/structured_query.proto index 4034be437f..c85a3203ee 100644 --- a/protos/perfetto/perfetto_sql/structured_query.proto +++ b/protos/perfetto/perfetto_sql/structured_query.proto @@ -211,15 +211,18 @@ message PerfettoSqlStructuredQuery { LESS_THAN_EQUAL = 4; GREATER_THAN = 5; GREATER_THAN_EQUAL = 6; + IS_NULL = 8; + IS_NOT_NULL = 9; // Unix GLOB. Only makes sense for string columns. GLOB = 7; } optional Operator op = 2; - // The RHS for filtering. All values specfied here will be ORed together - // allowing easy IN and GLOB IN filtering. At least one of these fields - // must be non-empty. Only the first non-empty field will be considered. + // The RHS for filtering. All values specified here will be ORed together + // allowing easy IN and GLOB IN filtering. If operation is different than + // IS_NULL or IS_NOT_NULL, at least one of these fields must be non-empty. + // Only the first non-empty field will be considered. repeated string string_rhs = 3; repeated double double_rhs = 4; repeated int64 int64_rhs = 5; diff --git a/src/trace_processor/perfetto_sql/generator/structured_query_generator.cc b/src/trace_processor/perfetto_sql/generator/structured_query_generator.cc index dd29eb0261..a9757308bb 100644 --- a/src/trace_processor/perfetto_sql/generator/structured_query_generator.cc +++ b/src/trace_processor/perfetto_sql/generator/structured_query_generator.cc @@ -323,28 +323,34 @@ base::StatusOr GeneratorImpl::Filters( } std::string column_name = filter.column_name().ToStdString(); + auto op = static_cast(filter.op()); + ASSIGN_OR_RETURN(std::string op_str, OperatorToString(op)); - ASSIGN_OR_RETURN( - std::string op, - OperatorToString( - static_cast(filter.op()))); - sql += column_name + " " + op + " "; + if (op == StructuredQuery::Filter::Operator::IS_NULL || + op == StructuredQuery::Filter::Operator::IS_NOT_NULL) { + sql += column_name + " " + op_str; + continue; + } + + sql += column_name + " " + op_str + " "; if (auto srhs = filter.string_rhs(); srhs) { sql += "'" + (*srhs++).ToStdString() + "'"; for (; srhs; ++srhs) { - sql += " OR " + column_name + " " + op + " '" + (*srhs).ToStdString() + - "'"; + sql += " OR " + column_name + " " + op_str + " '" + + (*srhs).ToStdString() + "'"; } } else if (auto drhs = filter.double_rhs(); drhs) { sql += std::to_string((*drhs++)); for (; drhs; ++drhs) { - sql += " OR " + column_name + " " + op + " " + std::to_string(*drhs); + sql += + " OR " + column_name + " " + op_str + " " + std::to_string(*drhs); } } else if (auto irhs = filter.int64_rhs(); irhs) { sql += std::to_string(*irhs++); for (; irhs; ++irhs) { - sql += " OR " + column_name + " " + op + " " + std::to_string(*irhs); + sql += + " OR " + column_name + " " + op_str + " " + std::to_string(*irhs); } } else { return base::ErrStatus("Filter must specify a right-hand side"); @@ -459,6 +465,10 @@ base::StatusOr GeneratorImpl::OperatorToString( return std::string(">="); case StructuredQuery::Filter::GLOB: return std::string("GLOB"); + case StructuredQuery::Filter::IS_NULL: + return std::string("IS NULL"); + case StructuredQuery::Filter::IS_NOT_NULL: + return std::string("IS NOT NULL"); case StructuredQuery::Filter::UNKNOWN: return base::ErrStatus("Invalid filter operator %d", op); } diff --git a/src/trace_processor/perfetto_sql/generator/structured_query_generator_unittest.cc b/src/trace_processor/perfetto_sql/generator/structured_query_generator_unittest.cc index a13b0cd720..b88d328653 100644 --- a/src/trace_processor/perfetto_sql/generator/structured_query_generator_unittest.cc +++ b/src/trace_processor/perfetto_sql/generator/structured_query_generator_unittest.cc @@ -60,6 +60,76 @@ MATCHER_P(EqualsIgnoringWhitespace, param, "") { return RemoveAllWhitespace(arg) == RemoveAllWhitespace(param); } +TEST(StructuredQueryGeneratorTest, Operations) { + StructuredQueryGenerator gen; + auto proto = ToProto(R"( + table: { + table_name: "thread_slice_cpu_time" + module_name: "linux.memory.process" + } + filters: { + column_name: "thread_name" + op: EQUAL + string_rhs: "bar" + } + filters: { + column_name: "thread_name" + op: NOT_EQUAL + string_rhs: "bar" + } + filters: { + column_name: "thread_name" + op: LESS_THAN + string_rhs: "bar" + } + filters: { + column_name: "thread_name" + op: LESS_THAN_EQUAL + string_rhs: "bar" + } + filters: { + column_name: "thread_name" + op: GREATER_THAN + string_rhs: "bar" + } + filters: { + column_name: "thread_name" + op: GREATER_THAN_EQUAL + string_rhs: "bar" + } + filters: { + column_name: "thread_name" + op: IS_NULL + } + filters: { + column_name: "thread_name" + op: IS_NOT_NULL + } + filters: { + column_name: "thread_name" + op: GLOB + string_rhs: "bar" + } + )"); + auto ret = gen.Generate(proto.data(), proto.size()); + ASSERT_OK_AND_ASSIGN(std::string res, ret); + ASSERT_THAT(res, EqualsIgnoringWhitespace(R"( + WITH sq_0 AS + ( + SELECT * FROM thread_slice_cpu_time + WHERE thread_name = 'bar' + AND thread_name != 'bar' + AND thread_name < 'bar' + AND thread_name <= 'bar' + AND thread_name > 'bar' + AND thread_name >= 'bar' + AND thread_name IS NULL + AND thread_name IS NOT NULL + AND thread_name GLOB 'bar' + ) SELECT * FROM sq_0 + )")); +} + TEST(StructuredQueryGeneratorTest, Smoke) { StructuredQueryGenerator gen; auto proto = ToProto(R"(