From e9222ab3e0db07660463bbc6e08c2654793131d2 Mon Sep 17 00:00:00 2001 From: Vibhu Pandey Date: Fri, 12 Sep 2025 17:46:04 +0530 Subject: [PATCH 1/9] docs(integration): add docs for writing integration tests (#9070) --- CONTRIBUTING.md | 3 +- docs/contributing/go/integration.md | 213 ++++++++++++++++++++++++++++ 2 files changed, 215 insertions(+), 1 deletion(-) create mode 100644 docs/contributing/go/integration.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4a2a85dc0dd5..8f49cccf6063 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -78,4 +78,5 @@ Need assistance? Join our Slack community: - Set up your [development environment](docs/contributing/development.md) - Deploy and observe [SigNoz in action with OpenTelemetry Demo Application](docs/otel-demo-docs.md) -- Explore the [SigNoz Community Advocate Program](ADVOCATE.md), which recognises contributors who support the community, share their expertise, and help shape SigNoz's future. \ No newline at end of file +- Explore the [SigNoz Community Advocate Program](ADVOCATE.md), which recognises contributors who support the community, share their expertise, and help shape SigNoz's future. +- Write [integration tests](docs/contributing/go/integration.md) diff --git a/docs/contributing/go/integration.md b/docs/contributing/go/integration.md new file mode 100644 index 000000000000..6e1074e6126b --- /dev/null +++ b/docs/contributing/go/integration.md @@ -0,0 +1,213 @@ +# Integration Tests + +SigNoz uses integration tests to verify that different components work together correctly in a real environment. These tests run against actual services (ClickHouse, PostgreSQL, etc.) to ensure end-to-end functionality. + +## How to set up the integration test environment? + +### Prerequisites + +Before running integration tests, ensure you have the following installed: + +- Python 3.13+ +- Poetry (for dependency management) +- Docker (for containerized services) + +### Initial Setup + +1. Navigate to the integration tests directory: +```bash +cd tests/integration +``` + +2. Install dependencies using Poetry: +```bash +poetry install --no-root +``` + +### Starting the Test Environment + +To spin up all the containers necessary for writing integration tests and keep them running: + +```bash +poetry run pytest --basetemp=./tmp/ -vv --reuse src/bootstrap/setup.py::test_setup +``` + +This command will: +- Start all required services (ClickHouse, PostgreSQL, Zookeeper, etc.) +- Keep containers running due to the `--reuse` flag +- Verify that the setup is working correctly + +### Stopping the Test Environment + +When you're done writing integration tests, clean up the environment: + +```bash +poetry run pytest --basetemp=./tmp/ -vv --teardown -s src/bootstrap/setup.py::test_teardown +``` + +This will destroy the running integration test setup and clean up resources. + +## Understanding the Integration Test Framework + +Python and pytest form the foundation of the integration testing framework. Testcontainers are used to spin up disposable integration environments. Wiremock is used to spin up **test doubles** of other services. + +- **Why Python/pytest?** It's expressive, low-boilerplate, and has powerful fixture capabilities that make integration testing straightforward. Extensive libraries for HTTP requests, JSON handling, and data analysis (numpy) make it easier to test APIs and verify data +- **Why testcontainers?** They let us spin up isolated dependencies that match our production environment without complex setup. +- **Why wiremock?** Well maintained, documented and extensible. + +``` +. +├── conftest.py +├── fixtures +│ ├── __init__.py +│ ├── auth.py +│ ├── clickhouse.py +│ ├── fs.py +│ ├── http.py +│ ├── migrator.py +│ ├── network.py +│ ├── postgres.py +│ ├── signoz.py +│ ├── sql.py +│ ├── sqlite.py +│ ├── types.py +│ └── zookeeper.py +├── poetry.lock +├── pyproject.toml +└── src + └── bootstrap + ├── __init__.py + ├── a_database.py + ├── b_register.py + └── c_license.py +``` + +Each test suite follows some important principles: + +1. **Organization**: Test suites live under `src/` in self-contained packages. Fixtures (a pytest concept) live inside `fixtures/`. +2. **Execution Order**: Files are prefixed with `a_`, `b_`, `c_` to ensure sequential execution. +3. **Time Constraints**: Each suite should complete in under 10 minutes (setup takes ~4 mins). + +### Test Suite Design + +Test suites should target functional domains or subsystems within SigNoz. When designing a test suite, consider these principles: + +- **Functional Cohesion**: Group tests around a specific capability or service boundary +- **Data Flow**: Follow the path of data through related components +- **Change Patterns**: Components frequently modified together should be tested together + +The exact boundaries for modules are intentionally flexible, allowing teams to define logical groupings based on their specific context and knowledge of the system. + +Eg: The **bootstrap** integration test suite validates core system functionality: + +- Database initialization +- Version check + +Other test suites can be **pipelines, auth, querier.** + +## How to write an integration test? + +Now start writing an integration test. Create a new file `src/bootstrap/e_version.py` and paste the following: + +```python +import requests + +from fixtures import types +from fixtures.logger import setup_logger + +logger = setup_logger(__name__) + +def test_version(signoz: types.SigNoz) -> None: + response = requests.get(signoz.self.host_config.get("/api/v1/version"), timeout=2) + logger.info(response) +``` + +We have written a simple test which calls the `version` endpoint of the container in step 1. In **order to just run this function, run the following command:** + +```bash +poetry run pytest --basetemp=./tmp/ -vv --reuse src/bootstrap/e_version.py::test_version +``` + +> Note: The `--reuse` flag is used to reuse the environment if it is already running. Always use this flag when writing and running integration tests. If you don't use this flag, the environment will be destroyed and recreated every time you run the test. + +Here's another example of how to write a more comprehensive integration test: + +```python +from http import HTTPStatus +import requests +from fixtures import types +from fixtures.logger import setup_logger + +logger = setup_logger(__name__) + +def test_user_registration(signoz: types.SigNoz) -> None: + """Test user registration functionality.""" + response = requests.post( + signoz.self.host_configs["8080"].get("/api/v1/register"), + json={ + "name": "testuser", + "orgId": "", + "orgName": "test.org", + "email": "test@example.com", + "password": "password123Z$", + }, + timeout=2, + ) + + assert response.status_code == HTTPStatus.OK + assert response.json()["setupCompleted"] is True +``` + +## How to run integration tests? + +### Running All Tests + +```bash +poetry run pytest --basetemp=./tmp/ -vv --reuse src/ +``` + +### Running Specific Test Categories + +```bash +poetry run pytest --basetemp=./tmp/ -vv --reuse src/ + +# Run querier tests +poetry run pytest --basetemp=./tmp/ -vv --reuse src/querier/ +# Run auth tests +poetry run pytest --basetemp=./tmp/ -vv --reuse src/auth/ +``` + +### Running Individual Tests + +```bash +poetry run pytest --basetemp=./tmp/ -vv --reuse src//.py::test_name + +# Run test_register in file a_register.py in auth suite +poetry run pytest --basetemp=./tmp/ -vv --reuse src/auth/a_register.py::test_register +``` + +## How to configure different options for integration tests? + +Tests can be configured using pytest options: + +- `--sqlstore-provider` - Choose database provider (default: postgres) +- `--postgres-version` - PostgreSQL version (default: 15) +- `--clickhouse-version` - ClickHouse version (default: 24.1.2-alpine) +- `--zookeeper-version` - Zookeeper version (default: 3.7.1) + +Example: +```bash +poetry run pytest --basetemp=./tmp/ -vv --reuse --sqlstore-provider=postgres --postgres-version=14 src/auth/ +``` + + +## What should I remember? + +- **Always use the `--reuse` flag** when setting up the environment to keep containers running +- **Use the `--teardown` flag** when cleaning up to avoid resource leaks +- **Follow the naming convention** with alphabetical prefixes for test execution order +- **Use proper timeouts** in HTTP requests to avoid hanging tests +- **Clean up test data** between tests to avoid interference +- **Use descriptive test names** that clearly indicate what is being tested +- **Leverage fixtures** for common setup and authentication +- **Test both success and failure scenarios** to ensure robust functionality From ae58915020efda32d0848eb64a0b6ac6de1a7827 Mon Sep 17 00:00:00 2001 From: Nityananda Gohain Date: Sun, 14 Sep 2025 18:18:39 +0530 Subject: [PATCH 2/9] chore: support for json column in resources (#8376) --- go.mod | 2 +- go.sum | 4 +- pkg/telemetrylogs/condition_builder.go | 7 + pkg/telemetrylogs/field_mapper.go | 24 ++- pkg/telemetrylogs/field_mapper_test.go | 18 ++- pkg/telemetrylogs/filter_expr_logs_test.go | 138 +++++++++--------- pkg/telemetrylogs/stmt_builder_test.go | 12 +- pkg/telemetrytraces/condition_builder.go | 7 + pkg/telemetrytraces/field_mapper.go | 23 ++- pkg/telemetrytraces/field_mapper_test.go | 14 +- pkg/telemetrytraces/stmt_builder_test.go | 24 +-- .../trace_operator_cte_builder_test.go | 10 +- 12 files changed, 177 insertions(+), 106 deletions(-) diff --git a/go.mod b/go.mod index 4fa125c5079c..042661cbbf63 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/ClickHouse/clickhouse-go/v2 v2.36.0 github.com/DATA-DOG/go-sqlmock v1.5.2 github.com/SigNoz/govaluate v0.0.0-20240203125216-988004ccc7fd - github.com/SigNoz/signoz-otel-collector v0.128.1 + github.com/SigNoz/signoz-otel-collector v0.129.4 github.com/antlr4-go/antlr/v4 v4.13.1 github.com/antonmedv/expr v1.15.3 github.com/cespare/xxhash/v2 v2.3.0 diff --git a/go.sum b/go.sum index 643e5a4a3e09..812cb8ce2cf7 100644 --- a/go.sum +++ b/go.sum @@ -104,8 +104,8 @@ github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/SigNoz/govaluate v0.0.0-20240203125216-988004ccc7fd h1:Bk43AsDYe0fhkbj57eGXx8H3ZJ4zhmQXBnrW523ktj8= github.com/SigNoz/govaluate v0.0.0-20240203125216-988004ccc7fd/go.mod h1:nxRcH/OEdM8QxzH37xkGzomr1O0JpYBRS6pwjsWW6Pc= -github.com/SigNoz/signoz-otel-collector v0.128.1 h1:D0bKMrRNgcKreKKYoakCr5jTWj1srupbNwGIvpHMihw= -github.com/SigNoz/signoz-otel-collector v0.128.1/go.mod h1:vFQLsJFzQwVkO1ltIMH+z9KKuTZTn/P0lKu2mNYDBpE= +github.com/SigNoz/signoz-otel-collector v0.129.4 h1:DGDu9y1I1FU+HX4eECPGmfhnXE4ys4yr7LL6znbf6to= +github.com/SigNoz/signoz-otel-collector v0.129.4/go.mod h1:xyR+coBzzO04p6Eu+ql2RVYUl/jFD+8hD9lArcc9U7g= github.com/Yiling-J/theine-go v0.6.1 h1:njE/rBBviU/Sq2G7PJKdLdwXg8j1azvZQulIjmshD+o= github.com/Yiling-J/theine-go v0.6.1/go.mod h1:08QpMa5JZ2pKN+UJCRrCasWYO1IKCdl54Xa836rpmDU= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= diff --git a/pkg/telemetrylogs/condition_builder.go b/pkg/telemetrylogs/condition_builder.go index 5c41938d5608..23dffd57bd57 100644 --- a/pkg/telemetrylogs/condition_builder.go +++ b/pkg/telemetrylogs/condition_builder.go @@ -165,6 +165,13 @@ func (c *conditionBuilder) conditionFor( var value any switch column.Type { + case schema.JSONColumnType{}: + value = "NULL" + if operator == qbtypes.FilterOperatorExists { + return sb.NE(tblFieldName, value), nil + } else { + return sb.E(tblFieldName, value), nil + } case schema.ColumnTypeString, schema.LowCardinalityColumnType{ElementType: schema.ColumnTypeString}: value = "" if operator == qbtypes.FilterOperatorExists { diff --git a/pkg/telemetrylogs/field_mapper.go b/pkg/telemetrylogs/field_mapper.go index cddf7b66d687..35d74f47ef8b 100644 --- a/pkg/telemetrylogs/field_mapper.go +++ b/pkg/telemetrylogs/field_mapper.go @@ -44,6 +44,7 @@ var ( KeyType: schema.LowCardinalityColumnType{ElementType: schema.ColumnTypeString}, ValueType: schema.ColumnTypeString, }}, + "resource": {Name: "resource", Type: schema.JSONColumnType{}}, "scope_name": {Name: "scope_name", Type: schema.ColumnTypeString}, "scope_version": {Name: "scope_version", Type: schema.ColumnTypeString}, "scope_string": {Name: "scope_string", Type: schema.MapColumnType{ @@ -53,7 +54,8 @@ var ( } ) -type fieldMapper struct{} +type fieldMapper struct { +} func NewFieldMapper() qbtypes.FieldMapper { return &fieldMapper{} @@ -62,7 +64,7 @@ func NewFieldMapper() qbtypes.FieldMapper { func (m *fieldMapper) getColumn(_ context.Context, key *telemetrytypes.TelemetryFieldKey) (*schema.Column, error) { switch key.FieldContext { case telemetrytypes.FieldContextResource: - return logsV2Columns["resources_string"], nil + return logsV2Columns["resource"], nil case telemetrytypes.FieldContextScope: switch key.Name { case "name", "scope.name", "scope_name": @@ -102,6 +104,24 @@ func (m *fieldMapper) FieldFor(ctx context.Context, key *telemetrytypes.Telemetr } switch column.Type { + case schema.JSONColumnType{}: + // json is only supported for resource context as of now + if key.FieldContext != telemetrytypes.FieldContextResource { + return "", errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "only resource context fields are supported for json columns, got %s", key.FieldContext.String) + } + oldColumn := logsV2Columns["resources_string"] + oldKeyName := fmt.Sprintf("%s['%s']", oldColumn.Name, key.Name) + + // have to add ::string as clickHouse throws an error :- data types Variant/Dynamic are not allowed in GROUP BY + // once clickHouse dependency is updated, we need to check if we can remove it. + if key.Materialized { + oldKeyName = telemetrytypes.FieldKeyToMaterializedColumnName(key) + oldKeyNameExists := telemetrytypes.FieldKeyToMaterializedColumnNameForExists(key) + return fmt.Sprintf("multiIf(%s.`%s` IS NOT NULL, %s.`%s`::String, %s==true, %s, NULL)", column.Name, key.Name, column.Name, key.Name, oldKeyNameExists, oldKeyName), nil + } else { + return fmt.Sprintf("multiIf(%s.`%s` IS NOT NULL, %s.`%s`::String, mapContains(%s, '%s'), %s, NULL)", column.Name, key.Name, column.Name, key.Name, oldColumn.Name, key.Name, oldKeyName), nil + } + case schema.ColumnTypeString, schema.LowCardinalityColumnType{ElementType: schema.ColumnTypeString}, schema.ColumnTypeUInt64, diff --git a/pkg/telemetrylogs/field_mapper_test.go b/pkg/telemetrylogs/field_mapper_test.go index f60a50a9ebd7..23b1ff2be8c9 100644 --- a/pkg/telemetrylogs/field_mapper_test.go +++ b/pkg/telemetrylogs/field_mapper_test.go @@ -26,7 +26,7 @@ func TestGetColumn(t *testing.T) { Name: "service.name", FieldContext: telemetrytypes.FieldContextResource, }, - expectedCol: logsV2Columns["resources_string"], + expectedCol: logsV2Columns["resource"], expectedError: nil, }, { @@ -234,7 +234,18 @@ func TestGetFieldKeyName(t *testing.T) { Name: "service.name", FieldContext: telemetrytypes.FieldContextResource, }, - expectedResult: "resources_string['service.name']", + expectedResult: "multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL)", + expectedError: nil, + }, + { + name: "Map column type - resource attribute - Materialized", + key: telemetrytypes.TelemetryFieldKey{ + Name: "service.name", + FieldContext: telemetrytypes.FieldContextResource, + FieldDataType: telemetrytypes.FieldDataTypeString, + Materialized: true, + }, + expectedResult: "multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, `resource_string_service$$name_exists`==true, `resource_string_service$$name`, NULL)", expectedError: nil, }, { @@ -248,10 +259,9 @@ func TestGetFieldKeyName(t *testing.T) { }, } - fm := NewFieldMapper() - for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { + fm := NewFieldMapper() result, err := fm.FieldFor(ctx, &tc.key) if tc.expectedError != nil { diff --git a/pkg/telemetrylogs/filter_expr_logs_test.go b/pkg/telemetrylogs/filter_expr_logs_test.go index 2f635a9088ac..2a46a96066f2 100644 --- a/pkg/telemetrylogs/filter_expr_logs_test.go +++ b/pkg/telemetrylogs/filter_expr_logs_test.go @@ -420,8 +420,8 @@ func TestFilterExprLogs(t *testing.T) { category: "FREETEXT with conditions", query: "error service.name=authentication", shouldPass: true, - expectedQuery: "WHERE (match(LOWER(body), LOWER(?)) AND (resources_string['service.name'] = ? AND mapContains(resources_string, 'service.name') = ?))", - expectedArgs: []any{"error", "authentication", true}, + expectedQuery: "WHERE (match(LOWER(body), LOWER(?)) AND (multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) = ? AND multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?))", + expectedArgs: []any{"error", "authentication", "NULL"}, expectedErrorContains: "", }, @@ -778,8 +778,8 @@ func TestFilterExprLogs(t *testing.T) { category: "Basic equality", query: "service.name=\"api\"", shouldPass: true, - expectedQuery: "WHERE (resources_string['service.name'] = ? AND mapContains(resources_string, 'service.name') = ?)", - expectedArgs: []any{"api", true}, + expectedQuery: "WHERE (multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) = ? AND multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?)", + expectedArgs: []any{"api", "NULL"}, expectedErrorContains: "", }, { @@ -844,7 +844,7 @@ func TestFilterExprLogs(t *testing.T) { category: "Not equals", query: "service.name!=\"api\"", shouldPass: true, - expectedQuery: "WHERE resources_string['service.name'] <> ?", + expectedQuery: "WHERE multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?", expectedArgs: []any{"api"}, expectedErrorContains: "", }, @@ -1138,16 +1138,16 @@ func TestFilterExprLogs(t *testing.T) { category: "IN operator (parentheses)", query: "service.name IN (\"api\", \"web\", \"auth\")", shouldPass: true, - expectedQuery: "WHERE ((resources_string['service.name'] = ? OR resources_string['service.name'] = ? OR resources_string['service.name'] = ?) AND mapContains(resources_string, 'service.name') = ?)", - expectedArgs: []any{"api", "web", "auth", true}, + expectedQuery: "WHERE ((multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) = ? OR multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) = ? OR multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) = ?) AND multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?)", + expectedArgs: []any{"api", "web", "auth", "NULL"}, expectedErrorContains: "", }, { category: "IN operator (parentheses)", query: "environment IN (\"dev\", \"test\", \"staging\", \"prod\")", shouldPass: true, - expectedQuery: "WHERE ((resources_string['environment'] = ? OR resources_string['environment'] = ? OR resources_string['environment'] = ? OR resources_string['environment'] = ?) AND mapContains(resources_string, 'environment') = ?)", - expectedArgs: []any{"dev", "test", "staging", "prod", true}, + expectedQuery: "WHERE ((multiIf(resource.`environment` IS NOT NULL, resource.`environment`::String, mapContains(resources_string, 'environment'), resources_string['environment'], NULL) = ? OR multiIf(resource.`environment` IS NOT NULL, resource.`environment`::String, mapContains(resources_string, 'environment'), resources_string['environment'], NULL) = ? OR multiIf(resource.`environment` IS NOT NULL, resource.`environment`::String, mapContains(resources_string, 'environment'), resources_string['environment'], NULL) = ? OR multiIf(resource.`environment` IS NOT NULL, resource.`environment`::String, mapContains(resources_string, 'environment'), resources_string['environment'], NULL) = ?) AND multiIf(resource.`environment` IS NOT NULL, resource.`environment`::String, mapContains(resources_string, 'environment'), resources_string['environment'], NULL) <> ?)", + expectedArgs: []any{"dev", "test", "staging", "prod", "NULL"}, expectedErrorContains: "", }, @@ -1172,16 +1172,16 @@ func TestFilterExprLogs(t *testing.T) { category: "IN operator (brackets)", query: "service.name IN [\"api\", \"web\", \"auth\"]", shouldPass: true, - expectedQuery: "WHERE ((resources_string['service.name'] = ? OR resources_string['service.name'] = ? OR resources_string['service.name'] = ?) AND mapContains(resources_string, 'service.name') = ?)", - expectedArgs: []any{"api", "web", "auth", true}, + expectedQuery: "WHERE ((multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) = ? OR multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) = ? OR multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) = ?) AND multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?)", + expectedArgs: []any{"api", "web", "auth", "NULL"}, expectedErrorContains: "", }, { category: "IN operator (brackets)", query: "environment IN [\"dev\", \"test\", \"staging\", \"prod\"]", shouldPass: true, - expectedQuery: "WHERE ((resources_string['environment'] = ? OR resources_string['environment'] = ? OR resources_string['environment'] = ? OR resources_string['environment'] = ?) AND mapContains(resources_string, 'environment') = ?)", - expectedArgs: []any{"dev", "test", "staging", "prod", true}, + expectedQuery: "WHERE ((multiIf(resource.`environment` IS NOT NULL, resource.`environment`::String, mapContains(resources_string, 'environment'), resources_string['environment'], NULL) = ? OR multiIf(resource.`environment` IS NOT NULL, resource.`environment`::String, mapContains(resources_string, 'environment'), resources_string['environment'], NULL) = ? OR multiIf(resource.`environment` IS NOT NULL, resource.`environment`::String, mapContains(resources_string, 'environment'), resources_string['environment'], NULL) = ? OR multiIf(resource.`environment` IS NOT NULL, resource.`environment`::String, mapContains(resources_string, 'environment'), resources_string['environment'], NULL) = ?) AND multiIf(resource.`environment` IS NOT NULL, resource.`environment`::String, mapContains(resources_string, 'environment'), resources_string['environment'], NULL) <> ?)", + expectedArgs: []any{"dev", "test", "staging", "prod", "NULL"}, expectedErrorContains: "", }, @@ -1206,7 +1206,7 @@ func TestFilterExprLogs(t *testing.T) { category: "NOT IN operator (parentheses)", query: "service.name NOT IN (\"database\", \"cache\")", shouldPass: true, - expectedQuery: "WHERE (resources_string['service.name'] <> ? AND resources_string['service.name'] <> ?)", + expectedQuery: "WHERE (multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ? AND multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?)", expectedArgs: []any{"database", "cache"}, expectedErrorContains: "", }, @@ -1214,7 +1214,7 @@ func TestFilterExprLogs(t *testing.T) { category: "NOT IN operator (parentheses)", query: "environment NOT IN (\"prod\")", shouldPass: true, - expectedQuery: "WHERE (resources_string['environment'] <> ?)", + expectedQuery: "WHERE (multiIf(resource.`environment` IS NOT NULL, resource.`environment`::String, mapContains(resources_string, 'environment'), resources_string['environment'], NULL) <> ?)", expectedArgs: []any{"prod"}, expectedErrorContains: "", }, @@ -1240,7 +1240,7 @@ func TestFilterExprLogs(t *testing.T) { category: "NOT IN operator (brackets)", query: "service.name NOT IN [\"database\", \"cache\"]", shouldPass: true, - expectedQuery: "WHERE (resources_string['service.name'] <> ? AND resources_string['service.name'] <> ?)", + expectedQuery: "WHERE (multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ? AND multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?)", expectedArgs: []any{"database", "cache"}, expectedErrorContains: "", }, @@ -1248,7 +1248,7 @@ func TestFilterExprLogs(t *testing.T) { category: "NOT IN operator (brackets)", query: "environment NOT IN [\"prod\"]", shouldPass: true, - expectedQuery: "WHERE (resources_string['environment'] <> ?)", + expectedQuery: "WHERE (multiIf(resource.`environment` IS NOT NULL, resource.`environment`::String, mapContains(resources_string, 'environment'), resources_string['environment'], NULL) <> ?)", expectedArgs: []any{"prod"}, expectedErrorContains: "", }, @@ -1498,8 +1498,8 @@ func TestFilterExprLogs(t *testing.T) { category: "Explicit AND", query: "status=200 AND service.name=\"api\"", shouldPass: true, - expectedQuery: "WHERE ((toFloat64(attributes_number['status']) = ? AND mapContains(attributes_number, 'status') = ?) AND (resources_string['service.name'] = ? AND mapContains(resources_string, 'service.name') = ?))", - expectedArgs: []any{float64(200), true, "api", true}, + expectedQuery: "WHERE ((toFloat64(attributes_number['status']) = ? AND mapContains(attributes_number, 'status') = ?) AND (multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) = ? AND multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?))", + expectedArgs: []any{float64(200), true, "api", "NULL"}, expectedErrorContains: "", }, { @@ -1532,8 +1532,8 @@ func TestFilterExprLogs(t *testing.T) { category: "Explicit OR", query: "service.name=\"api\" OR service.name=\"web\"", shouldPass: true, - expectedQuery: "WHERE ((resources_string['service.name'] = ? AND mapContains(resources_string, 'service.name') = ?) OR (resources_string['service.name'] = ? AND mapContains(resources_string, 'service.name') = ?))", - expectedArgs: []any{"api", true, "web", true}, + expectedQuery: "WHERE ((multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) = ? AND multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?) OR (multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) = ? AND multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?))", + expectedArgs: []any{"api", "NULL", "web", "NULL"}, expectedErrorContains: "", }, { @@ -1558,8 +1558,8 @@ func TestFilterExprLogs(t *testing.T) { category: "NOT with expressions", query: "NOT service.name=\"api\"", shouldPass: true, - expectedQuery: "WHERE NOT ((resources_string['service.name'] = ? AND mapContains(resources_string, 'service.name') = ?))", - expectedArgs: []any{"api", true}, + expectedQuery: "WHERE NOT ((multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) = ? AND multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?))", + expectedArgs: []any{"api", "NULL"}, expectedErrorContains: "", }, { @@ -1576,8 +1576,8 @@ func TestFilterExprLogs(t *testing.T) { category: "AND + OR combinations", query: "status=200 AND (service.name=\"api\" OR service.name=\"web\")", shouldPass: true, - expectedQuery: "WHERE ((toFloat64(attributes_number['status']) = ? AND mapContains(attributes_number, 'status') = ?) AND (((resources_string['service.name'] = ? AND mapContains(resources_string, 'service.name') = ?) OR (resources_string['service.name'] = ? AND mapContains(resources_string, 'service.name') = ?))))", - expectedArgs: []any{float64(200), true, "api", true, "web", true}, + expectedQuery: "WHERE ((toFloat64(attributes_number['status']) = ? AND mapContains(attributes_number, 'status') = ?) AND (((multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) = ? AND multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?) OR (multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) = ? AND multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?))))", + expectedArgs: []any{float64(200), true, "api", "NULL", "web", "NULL"}, expectedErrorContains: "", }, { @@ -1602,8 +1602,8 @@ func TestFilterExprLogs(t *testing.T) { category: "AND + NOT combinations", query: "status=200 AND NOT service.name=\"api\"", shouldPass: true, - expectedQuery: "WHERE ((toFloat64(attributes_number['status']) = ? AND mapContains(attributes_number, 'status') = ?) AND NOT ((resources_string['service.name'] = ? AND mapContains(resources_string, 'service.name') = ?)))", - expectedArgs: []any{float64(200), true, "api", true}, + expectedQuery: "WHERE ((toFloat64(attributes_number['status']) = ? AND mapContains(attributes_number, 'status') = ?) AND NOT ((multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) = ? AND multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?)))", + expectedArgs: []any{float64(200), true, "api", "NULL"}, expectedErrorContains: "", }, { @@ -1620,8 +1620,8 @@ func TestFilterExprLogs(t *testing.T) { category: "OR + NOT combinations", query: "NOT status=200 OR NOT service.name=\"api\"", shouldPass: true, - expectedQuery: "WHERE (NOT ((toFloat64(attributes_number['status']) = ? AND mapContains(attributes_number, 'status') = ?)) OR NOT ((resources_string['service.name'] = ? AND mapContains(resources_string, 'service.name') = ?)))", - expectedArgs: []any{float64(200), true, "api", true}, + expectedQuery: "WHERE (NOT ((toFloat64(attributes_number['status']) = ? AND mapContains(attributes_number, 'status') = ?)) OR NOT ((multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) = ? AND multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?)))", + expectedArgs: []any{float64(200), true, "api", "NULL"}, expectedErrorContains: "", }, { @@ -1638,8 +1638,8 @@ func TestFilterExprLogs(t *testing.T) { category: "AND + OR + NOT combinations", query: "status=200 AND (service.name=\"api\" OR NOT duration>1000)", shouldPass: true, - expectedQuery: "WHERE ((toFloat64(attributes_number['status']) = ? AND mapContains(attributes_number, 'status') = ?) AND (((resources_string['service.name'] = ? AND mapContains(resources_string, 'service.name') = ?) OR NOT ((toFloat64(attributes_number['duration']) > ? AND mapContains(attributes_number, 'duration') = ?)))))", - expectedArgs: []any{float64(200), true, "api", true, float64(1000), true}, + expectedQuery: "WHERE ((toFloat64(attributes_number['status']) = ? AND mapContains(attributes_number, 'status') = ?) AND (((multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) = ? AND multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?) OR NOT ((toFloat64(attributes_number['duration']) > ? AND mapContains(attributes_number, 'duration') = ?)))))", + expectedArgs: []any{float64(200), true, "api", "NULL", float64(1000), true}, expectedErrorContains: "", }, { @@ -1654,8 +1654,8 @@ func TestFilterExprLogs(t *testing.T) { category: "AND + OR + NOT combinations", query: "NOT (status=200 AND service.name=\"api\") OR count>0", shouldPass: true, - expectedQuery: "WHERE (NOT ((((toFloat64(attributes_number['status']) = ? AND mapContains(attributes_number, 'status') = ?) AND (resources_string['service.name'] = ? AND mapContains(resources_string, 'service.name') = ?)))) OR (toFloat64(attributes_number['count']) > ? AND mapContains(attributes_number, 'count') = ?))", - expectedArgs: []any{float64(200), true, "api", true, float64(0), true}, + expectedQuery: "WHERE (NOT ((((toFloat64(attributes_number['status']) = ? AND mapContains(attributes_number, 'status') = ?) AND (multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) = ? AND multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?)))) OR (toFloat64(attributes_number['count']) > ? AND mapContains(attributes_number, 'count') = ?))", + expectedArgs: []any{float64(200), true, "api", "NULL", float64(0), true}, expectedErrorContains: "", }, @@ -1664,8 +1664,8 @@ func TestFilterExprLogs(t *testing.T) { category: "Implicit AND", query: "status=200 service.name=\"api\"", shouldPass: true, - expectedQuery: "WHERE ((toFloat64(attributes_number['status']) = ? AND mapContains(attributes_number, 'status') = ?) AND (resources_string['service.name'] = ? AND mapContains(resources_string, 'service.name') = ?))", - expectedArgs: []any{float64(200), true, "api", true}, + expectedQuery: "WHERE ((toFloat64(attributes_number['status']) = ? AND mapContains(attributes_number, 'status') = ?) AND (multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) = ? AND multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?))", + expectedArgs: []any{float64(200), true, "api", "NULL"}, expectedErrorContains: "", }, { @@ -1690,8 +1690,8 @@ func TestFilterExprLogs(t *testing.T) { category: "Mixed implicit/explicit AND", query: "status=200 AND service.name=\"api\" duration<1000", shouldPass: true, - expectedQuery: "WHERE ((toFloat64(attributes_number['status']) = ? AND mapContains(attributes_number, 'status') = ?) AND (resources_string['service.name'] = ? AND mapContains(resources_string, 'service.name') = ?) AND (toFloat64(attributes_number['duration']) < ? AND mapContains(attributes_number, 'duration') = ?))", - expectedArgs: []any{float64(200), true, "api", true, float64(1000), true}, + expectedQuery: "WHERE ((toFloat64(attributes_number['status']) = ? AND mapContains(attributes_number, 'status') = ?) AND (multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) = ? AND multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?) AND (toFloat64(attributes_number['duration']) < ? AND mapContains(attributes_number, 'duration') = ?))", + expectedArgs: []any{float64(200), true, "api", "NULL", float64(1000), true}, expectedErrorContains: "", }, { @@ -1716,8 +1716,8 @@ func TestFilterExprLogs(t *testing.T) { category: "Simple grouping", query: "service.name=\"api\"", shouldPass: true, - expectedQuery: "WHERE (resources_string['service.name'] = ? AND mapContains(resources_string, 'service.name') = ?)", - expectedArgs: []any{"api", true}, + expectedQuery: "WHERE (multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) = ? AND multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?)", + expectedArgs: []any{"api", "NULL"}, expectedErrorContains: "", }, { @@ -1742,8 +1742,8 @@ func TestFilterExprLogs(t *testing.T) { category: "Nested grouping", query: "(((service.name=\"api\")))", shouldPass: true, - expectedQuery: "WHERE ((((resources_string['service.name'] = ? AND mapContains(resources_string, 'service.name') = ?))))", - expectedArgs: []any{"api", true}, + expectedQuery: "WHERE ((((multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) = ? AND multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?))))", + expectedArgs: []any{"api", "NULL"}, expectedErrorContains: "", }, { @@ -1760,8 +1760,8 @@ func TestFilterExprLogs(t *testing.T) { category: "Complex nested grouping", query: "(status=200 AND (service.name=\"api\" OR service.name=\"web\"))", shouldPass: true, - expectedQuery: "WHERE (((toFloat64(attributes_number['status']) = ? AND mapContains(attributes_number, 'status') = ?) AND (((resources_string['service.name'] = ? AND mapContains(resources_string, 'service.name') = ?) OR (resources_string['service.name'] = ? AND mapContains(resources_string, 'service.name') = ?)))))", - expectedArgs: []any{float64(200), true, "api", true, "web", true}, + expectedQuery: "WHERE (((toFloat64(attributes_number['status']) = ? AND mapContains(attributes_number, 'status') = ?) AND (((multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) = ? AND multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?) OR (multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) = ? AND multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?)))))", + expectedArgs: []any{float64(200), true, "api", "NULL", "web", "NULL"}, expectedErrorContains: "", }, { @@ -1786,16 +1786,16 @@ func TestFilterExprLogs(t *testing.T) { category: "Deep nesting", query: "(((status=200 OR status=201) AND service.name=\"api\") OR ((status=202 OR status=203) AND service.name=\"web\"))", shouldPass: true, - expectedQuery: "WHERE (((((((toFloat64(attributes_number['status']) = ? AND mapContains(attributes_number, 'status') = ?) OR (toFloat64(attributes_number['status']) = ? AND mapContains(attributes_number, 'status') = ?))) AND (resources_string['service.name'] = ? AND mapContains(resources_string, 'service.name') = ?))) OR (((((toFloat64(attributes_number['status']) = ? AND mapContains(attributes_number, 'status') = ?) OR (toFloat64(attributes_number['status']) = ? AND mapContains(attributes_number, 'status') = ?))) AND (resources_string['service.name'] = ? AND mapContains(resources_string, 'service.name') = ?)))))", - expectedArgs: []any{float64(200), true, float64(201), true, "api", true, float64(202), true, float64(203), true, "web", true}, + expectedQuery: "WHERE (((((((toFloat64(attributes_number['status']) = ? AND mapContains(attributes_number, 'status') = ?) OR (toFloat64(attributes_number['status']) = ? AND mapContains(attributes_number, 'status') = ?))) AND (multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) = ? AND multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?))) OR (((((toFloat64(attributes_number['status']) = ? AND mapContains(attributes_number, 'status') = ?) OR (toFloat64(attributes_number['status']) = ? AND mapContains(attributes_number, 'status') = ?))) AND (multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) = ? AND multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?)))))", + expectedArgs: []any{float64(200), true, float64(201), true, "api", "NULL", float64(202), true, float64(203), true, "web", "NULL"}, expectedErrorContains: "", }, { category: "Deep nesting", query: "(count>0 AND ((duration<1000 AND service.name=\"api\") OR (duration<500 AND service.name=\"web\")))", shouldPass: true, - expectedQuery: "WHERE (((toFloat64(attributes_number['count']) > ? AND mapContains(attributes_number, 'count') = ?) AND (((((toFloat64(attributes_number['duration']) < ? AND mapContains(attributes_number, 'duration') = ?) AND (resources_string['service.name'] = ? AND mapContains(resources_string, 'service.name') = ?))) OR (((toFloat64(attributes_number['duration']) < ? AND mapContains(attributes_number, 'duration') = ?) AND (resources_string['service.name'] = ? AND mapContains(resources_string, 'service.name') = ?)))))))", - expectedArgs: []any{float64(0), true, float64(1000), true, "api", true, float64(500), true, "web", true}, + expectedQuery: "WHERE (((toFloat64(attributes_number['count']) > ? AND mapContains(attributes_number, 'count') = ?) AND (((((toFloat64(attributes_number['duration']) < ? AND mapContains(attributes_number, 'duration') = ?) AND (multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) = ? AND multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?))) OR (((toFloat64(attributes_number['duration']) < ? AND mapContains(attributes_number, 'duration') = ?) AND (multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) = ? AND multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?)))))))", + expectedArgs: []any{float64(0), true, float64(1000), true, "api", "NULL", float64(500), true, "web", "NULL"}, expectedErrorContains: "", }, @@ -1804,16 +1804,16 @@ func TestFilterExprLogs(t *testing.T) { category: "String quote styles", query: "service.name=\"api\"", shouldPass: true, - expectedQuery: "WHERE (resources_string['service.name'] = ? AND mapContains(resources_string, 'service.name') = ?)", - expectedArgs: []any{"api", true}, + expectedQuery: "WHERE (multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) = ? AND multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?)", + expectedArgs: []any{"api", "NULL"}, expectedErrorContains: "", }, { category: "String quote styles", query: "service.name='api'", shouldPass: true, - expectedQuery: "WHERE (resources_string['service.name'] = ? AND mapContains(resources_string, 'service.name') = ?)", - expectedArgs: []any{"api", true}, + expectedQuery: "WHERE (multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) = ? AND multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?)", + expectedArgs: []any{"api", "NULL"}, expectedErrorContains: "", }, { @@ -1972,29 +1972,29 @@ func TestFilterExprLogs(t *testing.T) { category: "Operator precedence", query: "NOT status=200 AND service.name=\"api\"", shouldPass: true, - expectedQuery: "WHERE (NOT ((toFloat64(attributes_number['status']) = ? AND mapContains(attributes_number, 'status') = ?)) AND (resources_string['service.name'] = ? AND mapContains(resources_string, 'service.name') = ?))", - expectedArgs: []any{float64(200), true, "api", true}, // Should be (NOT status=200) AND service.name="api" + expectedQuery: "WHERE (NOT ((toFloat64(attributes_number['status']) = ? AND mapContains(attributes_number, 'status') = ?)) AND (multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) = ? AND multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?))", + expectedArgs: []any{float64(200), true, "api", "NULL"}, // Should be (NOT status=200) AND service.name="api" }, { category: "Operator precedence", query: "status=200 AND service.name=\"api\" OR service.name=\"web\"", shouldPass: true, - expectedQuery: "WHERE (((toFloat64(attributes_number['status']) = ? AND mapContains(attributes_number, 'status') = ?) AND (resources_string['service.name'] = ? AND mapContains(resources_string, 'service.name') = ?)) OR (resources_string['service.name'] = ? AND mapContains(resources_string, 'service.name') = ?))", - expectedArgs: []any{float64(200), true, "api", true, "web", true}, // Should be (status=200 AND service.name="api") OR service.name="web" + expectedQuery: "WHERE (((toFloat64(attributes_number['status']) = ? AND mapContains(attributes_number, 'status') = ?) AND (multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) = ? AND multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?)) OR (multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) = ? AND multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?))", + expectedArgs: []any{float64(200), true, "api", "NULL", "web", "NULL"}, // Should be (status=200 AND service.name="api") OR service.name="web" }, { category: "Operator precedence", query: "NOT status=200 OR NOT service.name=\"api\"", shouldPass: true, - expectedQuery: "WHERE (NOT ((toFloat64(attributes_number['status']) = ? AND mapContains(attributes_number, 'status') = ?)) OR NOT ((resources_string['service.name'] = ? AND mapContains(resources_string, 'service.name') = ?)))", - expectedArgs: []any{float64(200), true, "api", true}, // Should be (NOT status=200) OR (NOT service.name="api") + expectedQuery: "WHERE (NOT ((toFloat64(attributes_number['status']) = ? AND mapContains(attributes_number, 'status') = ?)) OR NOT ((multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) = ? AND multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?)))", + expectedArgs: []any{float64(200), true, "api", "NULL"}, // Should be (NOT status=200) OR (NOT service.name="api") }, { category: "Operator precedence", query: "status=200 OR service.name=\"api\" AND level=\"ERROR\"", shouldPass: true, - expectedQuery: "WHERE ((toFloat64(attributes_number['status']) = ? AND mapContains(attributes_number, 'status') = ?) OR ((resources_string['service.name'] = ? AND mapContains(resources_string, 'service.name') = ?) AND (attributes_string['level'] = ? AND mapContains(attributes_string, 'level') = ?)))", - expectedArgs: []any{float64(200), true, "api", true, "ERROR", true}, // Should be status=200 OR (service.name="api" AND level="ERROR") + expectedQuery: "WHERE ((toFloat64(attributes_number['status']) = ? AND mapContains(attributes_number, 'status') = ?) OR ((multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) = ? AND multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?) AND (attributes_string['level'] = ? AND mapContains(attributes_string, 'level') = ?)))", + expectedArgs: []any{float64(200), true, "api", "NULL", "ERROR", true}, // Should be status=200 OR (service.name="api" AND level="ERROR") }, // Different whitespace patterns @@ -2018,8 +2018,8 @@ func TestFilterExprLogs(t *testing.T) { category: "Whitespace patterns", query: "status=200 AND service.name=\"api\"", shouldPass: true, - expectedQuery: "WHERE ((toFloat64(attributes_number['status']) = ? AND mapContains(attributes_number, 'status') = ?) AND (resources_string['service.name'] = ? AND mapContains(resources_string, 'service.name') = ?))", - expectedArgs: []any{float64(200), true, "api", true}, // Multiple spaces + expectedQuery: "WHERE ((toFloat64(attributes_number['status']) = ? AND mapContains(attributes_number, 'status') = ?) AND (multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) = ? AND multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?))", + expectedArgs: []any{float64(200), true, "api", "NULL"}, // Multiple spaces }, // More Unicode characters @@ -2188,8 +2188,8 @@ func TestFilterExprLogs(t *testing.T) { category: "More common filters", query: "service.name=\"api\" AND (status>=500 OR duration>1000) AND NOT message CONTAINS \"expected\"", shouldPass: true, - expectedQuery: "WHERE ((resources_string['service.name'] = ? AND mapContains(resources_string, 'service.name') = ?) AND (((toFloat64(attributes_number['status']) >= ? AND mapContains(attributes_number, 'status') = ?) OR (toFloat64(attributes_number['duration']) > ? AND mapContains(attributes_number, 'duration') = ?))) AND NOT ((LOWER(attributes_string['message']) LIKE LOWER(?) AND mapContains(attributes_string, 'message') = ?)))", - expectedArgs: []any{"api", true, float64(500), true, float64(1000), true, "%expected%", true}, + expectedQuery: "WHERE ((multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) = ? AND multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?) AND (((toFloat64(attributes_number['status']) >= ? AND mapContains(attributes_number, 'status') = ?) OR (toFloat64(attributes_number['duration']) > ? AND mapContains(attributes_number, 'duration') = ?))) AND NOT ((LOWER(attributes_string['message']) LIKE LOWER(?) AND mapContains(attributes_string, 'message') = ?)))", + expectedArgs: []any{"api", "NULL", float64(500), true, float64(1000), true, "%expected%", true}, }, // Edge cases @@ -2254,8 +2254,8 @@ func TestFilterExprLogs(t *testing.T) { category: "Unusual whitespace", query: "status = 200 AND service.name = \"api\"", shouldPass: true, - expectedQuery: "WHERE ((toFloat64(attributes_number['status']) = ? AND mapContains(attributes_number, 'status') = ?) AND (resources_string['service.name'] = ? AND mapContains(resources_string, 'service.name') = ?))", - expectedArgs: []any{float64(200), true, "api", true}, + expectedQuery: "WHERE ((toFloat64(attributes_number['status']) = ? AND mapContains(attributes_number, 'status') = ?) AND (multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) = ? AND multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?))", + expectedArgs: []any{float64(200), true, "api", "NULL"}, }, { category: "Unusual whitespace", @@ -2315,13 +2315,13 @@ func TestFilterExprLogs(t *testing.T) { ) `, shouldPass: true, - expectedQuery: "WHERE ((((((((toFloat64(attributes_number['status']) >= ? AND mapContains(attributes_number, 'status') = ?) AND (toFloat64(attributes_number['status']) < ? AND mapContains(attributes_number, 'status') = ?))) OR (((toFloat64(attributes_number['status']) >= ? AND mapContains(attributes_number, 'status') = ?) AND (toFloat64(attributes_number['status']) < ? AND mapContains(attributes_number, 'status') = ?) AND NOT ((toFloat64(attributes_number['status']) = ? AND mapContains(attributes_number, 'status') = ?)))))) AND ((((resources_string['service.name'] = ? OR resources_string['service.name'] = ? OR resources_string['service.name'] = ?) AND mapContains(resources_string, 'service.name') = ?) OR (((resources_string['service.type'] = ? AND mapContains(resources_string, 'service.type') = ?) AND NOT ((resources_string['service.deprecated'] = ? AND mapContains(resources_string, 'service.deprecated') = ?)))))))) AND (((((toFloat64(attributes_number['duration']) < ? AND mapContains(attributes_number, 'duration') = ?) OR ((toFloat64(attributes_number['duration']) BETWEEN ? AND ? AND mapContains(attributes_number, 'duration') = ?)))) AND ((resources_string['environment'] <> ? OR (((resources_string['environment'] = ? AND mapContains(resources_string, 'environment') = ?) AND (attributes_bool['is_automated_test'] = ? AND mapContains(attributes_bool, 'is_automated_test') = ?))))))) AND NOT ((((((LOWER(attributes_string['message']) LIKE LOWER(?) AND mapContains(attributes_string, 'message') = ?) OR (LOWER(attributes_string['message']) LIKE LOWER(?) AND mapContains(attributes_string, 'message') = ?))) AND (attributes_string['severity'] = ? AND mapContains(attributes_string, 'severity') = ?)))))", + expectedQuery: "WHERE ((((((((toFloat64(attributes_number['status']) >= ? AND mapContains(attributes_number, 'status') = ?) AND (toFloat64(attributes_number['status']) < ? AND mapContains(attributes_number, 'status') = ?))) OR (((toFloat64(attributes_number['status']) >= ? AND mapContains(attributes_number, 'status') = ?) AND (toFloat64(attributes_number['status']) < ? AND mapContains(attributes_number, 'status') = ?) AND NOT ((toFloat64(attributes_number['status']) = ? AND mapContains(attributes_number, 'status') = ?)))))) AND ((((multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) = ? OR multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) = ? OR multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) = ?) AND multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?) OR (((multiIf(resource.`service.type` IS NOT NULL, resource.`service.type`::String, mapContains(resources_string, 'service.type'), resources_string['service.type'], NULL) = ? AND multiIf(resource.`service.type` IS NOT NULL, resource.`service.type`::String, mapContains(resources_string, 'service.type'), resources_string['service.type'], NULL) <> ?) AND NOT ((multiIf(resource.`service.deprecated` IS NOT NULL, resource.`service.deprecated`::String, mapContains(resources_string, 'service.deprecated'), resources_string['service.deprecated'], NULL) = ? AND multiIf(resource.`service.deprecated` IS NOT NULL, resource.`service.deprecated`::String, mapContains(resources_string, 'service.deprecated'), resources_string['service.deprecated'], NULL) <> ?)))))))) AND (((((toFloat64(attributes_number['duration']) < ? AND mapContains(attributes_number, 'duration') = ?) OR ((toFloat64(attributes_number['duration']) BETWEEN ? AND ? AND mapContains(attributes_number, 'duration') = ?)))) AND ((multiIf(resource.`environment` IS NOT NULL, resource.`environment`::String, mapContains(resources_string, 'environment'), resources_string['environment'], NULL) <> ? OR (((multiIf(resource.`environment` IS NOT NULL, resource.`environment`::String, mapContains(resources_string, 'environment'), resources_string['environment'], NULL) = ? AND multiIf(resource.`environment` IS NOT NULL, resource.`environment`::String, mapContains(resources_string, 'environment'), resources_string['environment'], NULL) <> ?) AND (attributes_bool['is_automated_test'] = ? AND mapContains(attributes_bool, 'is_automated_test') = ?))))))) AND NOT ((((((LOWER(attributes_string['message']) LIKE LOWER(?) AND mapContains(attributes_string, 'message') = ?) OR (LOWER(attributes_string['message']) LIKE LOWER(?) AND mapContains(attributes_string, 'message') = ?))) AND (attributes_string['severity'] = ? AND mapContains(attributes_string, 'severity') = ?)))))", expectedArgs: []any{ float64(200), true, float64(300), true, float64(400), true, float64(500), true, float64(404), true, - "api", "web", "auth", true, - "internal", true, true, true, + "api", "web", "auth", "NULL", + "internal", "NULL", true, "NULL", float64(1000), true, float64(1000), float64(5000), true, - "test", "test", true, true, true, + "test", "test", "NULL", true, true, "%warning%", true, "%deprecated%", true, "low", true, }, diff --git a/pkg/telemetrylogs/stmt_builder_test.go b/pkg/telemetrylogs/stmt_builder_test.go index ed23fafc183c..1610a32a10cf 100644 --- a/pkg/telemetrylogs/stmt_builder_test.go +++ b/pkg/telemetrylogs/stmt_builder_test.go @@ -69,8 +69,8 @@ func TestStatementBuilderTimeSeries(t *testing.T) { }, }, expected: qbtypes.Statement{ - Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_logs.distributed_logs_v2_resource WHERE (simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?), __limit_cte AS (SELECT toString(multiIf(mapContains(resources_string, 'service.name') = ?, resources_string['service.name'], NULL)) AS `service.name`, count() AS __result_0 FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? GROUP BY `service.name` ORDER BY __result_0 DESC LIMIT ?) SELECT toStartOfInterval(fromUnixTimestamp64Nano(timestamp), INTERVAL 30 SECOND) AS ts, toString(multiIf(mapContains(resources_string, 'service.name') = ?, resources_string['service.name'], NULL)) AS `service.name`, count() AS __result_0 FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? AND (`service.name`) GLOBAL IN (SELECT `service.name` FROM __limit_cte) GROUP BY ts, `service.name`", - Args: []any{"cartservice", "%service.name%", "%service.name\":\"cartservice%", uint64(1747945619), uint64(1747983448), true, "1747947419000000000", uint64(1747945619), "1747983448000000000", uint64(1747983448), 10, true, "1747947419000000000", uint64(1747945619), "1747983448000000000", uint64(1747983448)}, + Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_logs.distributed_logs_v2_resource WHERE (simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?), __limit_cte AS (SELECT toString(multiIf(multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL), NULL)) AS `service.name`, count() AS __result_0 FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? GROUP BY `service.name` ORDER BY __result_0 DESC LIMIT ?) SELECT toStartOfInterval(fromUnixTimestamp64Nano(timestamp), INTERVAL 30 SECOND) AS ts, toString(multiIf(multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL), NULL)) AS `service.name`, count() AS __result_0 FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? AND (`service.name`) GLOBAL IN (SELECT `service.name` FROM __limit_cte) GROUP BY ts, `service.name`", + Args: []any{"cartservice", "%service.name%", "%service.name\":\"cartservice%", uint64(1747945619), uint64(1747983448), "NULL", "1747947419000000000", uint64(1747945619), "1747983448000000000", uint64(1747983448), 10, "NULL", "1747947419000000000", uint64(1747945619), "1747983448000000000", uint64(1747983448)}, }, expectedErr: nil, }, @@ -98,8 +98,8 @@ func TestStatementBuilderTimeSeries(t *testing.T) { }, }, expected: qbtypes.Statement{ - Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_logs.distributed_logs_v2_resource WHERE ((simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) OR true) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?), __limit_cte AS (SELECT toString(multiIf(mapContains(resources_string, 'service.name') = ?, resources_string['service.name'], NULL)) AS `service.name`, count() AS __result_0 FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND ((resources_string['service.name'] = ? AND mapContains(resources_string, 'service.name') = ?) OR (attributes_string['http.method'] = ? AND mapContains(attributes_string, 'http.method') = ?)) AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? GROUP BY `service.name` ORDER BY __result_0 DESC LIMIT ?) SELECT toStartOfInterval(fromUnixTimestamp64Nano(timestamp), INTERVAL 30 SECOND) AS ts, toString(multiIf(mapContains(resources_string, 'service.name') = ?, resources_string['service.name'], NULL)) AS `service.name`, count() AS __result_0 FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND ((resources_string['service.name'] = ? AND mapContains(resources_string, 'service.name') = ?) OR (attributes_string['http.method'] = ? AND mapContains(attributes_string, 'http.method') = ?)) AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? AND (`service.name`) GLOBAL IN (SELECT `service.name` FROM __limit_cte) GROUP BY ts, `service.name`", - Args: []any{"redis-manual", "%service.name%", "%service.name\":\"redis-manual%", uint64(1747945619), uint64(1747983448), true, "redis-manual", true, "GET", true, "1747947419000000000", uint64(1747945619), "1747983448000000000", uint64(1747983448), 10, true, "redis-manual", true, "GET", true, "1747947419000000000", uint64(1747945619), "1747983448000000000", uint64(1747983448)}, + Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_logs.distributed_logs_v2_resource WHERE ((simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) OR true) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?), __limit_cte AS (SELECT toString(multiIf(multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL), NULL)) AS `service.name`, count() AS __result_0 FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND ((multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) = ? AND multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?) OR (attributes_string['http.method'] = ? AND mapContains(attributes_string, 'http.method') = ?)) AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? GROUP BY `service.name` ORDER BY __result_0 DESC LIMIT ?) SELECT toStartOfInterval(fromUnixTimestamp64Nano(timestamp), INTERVAL 30 SECOND) AS ts, toString(multiIf(multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL), NULL)) AS `service.name`, count() AS __result_0 FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND ((multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) = ? AND multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?) OR (attributes_string['http.method'] = ? AND mapContains(attributes_string, 'http.method') = ?)) AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? AND (`service.name`) GLOBAL IN (SELECT `service.name` FROM __limit_cte) GROUP BY ts, `service.name`", + Args: []any{"redis-manual", "%service.name%", "%service.name\":\"redis-manual%", uint64(1747945619), uint64(1747983448), "NULL", "redis-manual", "NULL", "GET", true, "1747947419000000000", uint64(1747945619), "1747983448000000000", uint64(1747983448), 10, "NULL", "redis-manual", "NULL", "GET", true, "1747947419000000000", uint64(1747945619), "1747983448000000000", uint64(1747983448)}, }, expectedErr: nil, }, @@ -137,8 +137,8 @@ func TestStatementBuilderTimeSeries(t *testing.T) { }, }, expected: qbtypes.Statement{ - Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_logs.distributed_logs_v2_resource WHERE (simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?), __limit_cte AS (SELECT toString(multiIf(mapContains(resources_string, 'service.name') = ?, resources_string['service.name'], NULL)) AS `service.name`, count() AS __result_0 FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? GROUP BY `service.name` ORDER BY `service.name` desc LIMIT ?) SELECT toStartOfInterval(fromUnixTimestamp64Nano(timestamp), INTERVAL 30 SECOND) AS ts, toString(multiIf(mapContains(resources_string, 'service.name') = ?, resources_string['service.name'], NULL)) AS `service.name`, count() AS __result_0 FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? AND (`service.name`) GLOBAL IN (SELECT `service.name` FROM __limit_cte) GROUP BY ts, `service.name` ORDER BY `service.name` desc, ts desc", - Args: []any{"cartservice", "%service.name%", "%service.name\":\"cartservice%", uint64(1747945619), uint64(1747983448), true, "1747947419000000000", uint64(1747945619), "1747983448000000000", uint64(1747983448), 10, true, "1747947419000000000", uint64(1747945619), "1747983448000000000", uint64(1747983448)}, + Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_logs.distributed_logs_v2_resource WHERE (simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?), __limit_cte AS (SELECT toString(multiIf(multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL), NULL)) AS `service.name`, count() AS __result_0 FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? GROUP BY `service.name` ORDER BY `service.name` desc LIMIT ?) SELECT toStartOfInterval(fromUnixTimestamp64Nano(timestamp), INTERVAL 30 SECOND) AS ts, toString(multiIf(multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL), NULL)) AS `service.name`, count() AS __result_0 FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? AND (`service.name`) GLOBAL IN (SELECT `service.name` FROM __limit_cte) GROUP BY ts, `service.name` ORDER BY `service.name` desc, ts desc", + Args: []any{"cartservice", "%service.name%", "%service.name\":\"cartservice%", uint64(1747945619), uint64(1747983448), "NULL", "1747947419000000000", uint64(1747945619), "1747983448000000000", uint64(1747983448), 10, "NULL", "1747947419000000000", uint64(1747945619), "1747983448000000000", uint64(1747983448)}, }, expectedErr: nil, }, diff --git a/pkg/telemetrytraces/condition_builder.go b/pkg/telemetrytraces/condition_builder.go index d7e0e7027ac6..84bc3bee4d2d 100644 --- a/pkg/telemetrytraces/condition_builder.go +++ b/pkg/telemetrytraces/condition_builder.go @@ -163,6 +163,13 @@ func (c *conditionBuilder) conditionFor( var value any switch column.Type { + case schema.JSONColumnType{}: + value = "NULL" + if operator == qbtypes.FilterOperatorExists { + return sb.NE(tblFieldName, value), nil + } else { + return sb.E(tblFieldName, value), nil + } case schema.ColumnTypeString, schema.LowCardinalityColumnType{ElementType: schema.ColumnTypeString}, schema.FixedStringColumnType{Length: 32}, diff --git a/pkg/telemetrytraces/field_mapper.go b/pkg/telemetrytraces/field_mapper.go index d30d25d83752..a75fb417b03e 100644 --- a/pkg/telemetrytraces/field_mapper.go +++ b/pkg/telemetrytraces/field_mapper.go @@ -50,6 +50,7 @@ var ( KeyType: schema.LowCardinalityColumnType{ElementType: schema.ColumnTypeString}, ValueType: schema.ColumnTypeString, }}, + "resource": {Name: "resource", Type: schema.JSONColumnType{}}, "events": {Name: "events", Type: schema.ArrayColumnType{ ElementType: schema.ColumnTypeString, @@ -157,7 +158,8 @@ var ( } ) -type defaultFieldMapper struct{} +type defaultFieldMapper struct { +} var _ qbtypes.FieldMapper = (*defaultFieldMapper)(nil) @@ -171,7 +173,7 @@ func (m *defaultFieldMapper) getColumn( ) (*schema.Column, error) { switch key.FieldContext { case telemetrytypes.FieldContextResource: - return indexV3Columns["resources_string"], nil + return indexV3Columns["resource"], nil case telemetrytypes.FieldContextScope: return nil, qbtypes.ErrColumnNotFound case telemetrytypes.FieldContextAttribute: @@ -235,6 +237,23 @@ func (m *defaultFieldMapper) FieldFor( } switch column.Type { + case schema.JSONColumnType{}: + // json is only supported for resource context as of now + if key.FieldContext != telemetrytypes.FieldContextResource { + return "", errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "only resource context fields are supported for json columns, got %s", key.FieldContext.String) + } + oldColumn := indexV3Columns["resources_string"] + oldKeyName := fmt.Sprintf("%s['%s']", oldColumn.Name, key.Name) + // have to add ::string as clickHouse throws an error :- data types Variant/Dynamic are not allowed in GROUP BY + // once clickHouse dependency is updated, we need to check if we can remove it. + if key.Materialized { + oldKeyName = telemetrytypes.FieldKeyToMaterializedColumnName(key) + oldKeyNameExists := telemetrytypes.FieldKeyToMaterializedColumnNameForExists(key) + return fmt.Sprintf("multiIf(%s.`%s` IS NOT NULL, %s.`%s`::String, %s==true, %s, NULL)", column.Name, key.Name, column.Name, key.Name, oldKeyNameExists, oldKeyName), nil + } else { + return fmt.Sprintf("multiIf(%s.`%s` IS NOT NULL, %s.`%s`::String, mapContains(%s, '%s'), %s, NULL)", column.Name, key.Name, column.Name, key.Name, oldColumn.Name, key.Name, oldKeyName), nil + } + case schema.ColumnTypeString, schema.LowCardinalityColumnType{ElementType: schema.ColumnTypeString}, schema.ColumnTypeUInt64, diff --git a/pkg/telemetrytraces/field_mapper_test.go b/pkg/telemetrytraces/field_mapper_test.go index 472daadada49..93e473e1cbe8 100644 --- a/pkg/telemetrytraces/field_mapper_test.go +++ b/pkg/telemetrytraces/field_mapper_test.go @@ -64,7 +64,16 @@ func TestGetFieldKeyName(t *testing.T) { Name: "service.name", FieldContext: telemetrytypes.FieldContextResource, }, - expectedResult: "resources_string['service.name']", + expectedResult: "multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL)", + expectedError: nil, + }, + { + name: "Map column type - resource attribute - legacy", + key: telemetrytypes.TelemetryFieldKey{ + Name: "service.name", + FieldContext: telemetrytypes.FieldContextResource, + }, + expectedResult: "multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL)", expectedError: nil, }, { @@ -78,10 +87,9 @@ func TestGetFieldKeyName(t *testing.T) { }, } - fm := NewFieldMapper() - for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { + fm := NewFieldMapper() result, err := fm.FieldFor(ctx, &tc.key) if tc.expectedError != nil { diff --git a/pkg/telemetrytraces/stmt_builder_test.go b/pkg/telemetrytraces/stmt_builder_test.go index ea1a8e43eab9..ba089266f34b 100644 --- a/pkg/telemetrytraces/stmt_builder_test.go +++ b/pkg/telemetrytraces/stmt_builder_test.go @@ -60,8 +60,8 @@ func TestStatementBuilder(t *testing.T) { }, }, expected: qbtypes.Statement{ - Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?), __limit_cte AS (SELECT toString(multiIf(mapContains(resources_string, 'service.name') = ?, resources_string['service.name'], NULL)) AS `service.name`, count() AS __result_0 FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? GROUP BY `service.name` ORDER BY __result_0 DESC LIMIT ?) SELECT toStartOfInterval(timestamp, INTERVAL 30 SECOND) AS ts, toString(multiIf(mapContains(resources_string, 'service.name') = ?, resources_string['service.name'], NULL)) AS `service.name`, count() AS __result_0 FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? AND (`service.name`) GLOBAL IN (SELECT `service.name` FROM __limit_cte) GROUP BY ts, `service.name`", - Args: []any{"redis-manual", "%service.name%", "%service.name\":\"redis-manual%", uint64(1747945619), uint64(1747983448), true, "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), 10, true, "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448)}, + Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?), __limit_cte AS (SELECT toString(multiIf(multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL), NULL)) AS `service.name`, count() AS __result_0 FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? GROUP BY `service.name` ORDER BY __result_0 DESC LIMIT ?) SELECT toStartOfInterval(timestamp, INTERVAL 30 SECOND) AS ts, toString(multiIf(multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL), NULL)) AS `service.name`, count() AS __result_0 FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? AND (`service.name`) GLOBAL IN (SELECT `service.name` FROM __limit_cte) GROUP BY ts, `service.name`", + Args: []any{"redis-manual", "%service.name%", "%service.name\":\"redis-manual%", uint64(1747945619), uint64(1747983448), "NULL", "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), 10, "NULL", "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448)}, }, expectedErr: nil, }, @@ -89,8 +89,8 @@ func TestStatementBuilder(t *testing.T) { }, }, expected: qbtypes.Statement{ - Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE ((simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) OR true) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?), __limit_cte AS (SELECT toString(multiIf(mapContains(resources_string, 'service.name') = ?, resources_string['service.name'], NULL)) AS `service.name`, count() AS __result_0 FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND ((resources_string['service.name'] = ? AND mapContains(resources_string, 'service.name') = ?) OR (attributes_string['http.request.method'] = ? AND mapContains(attributes_string, 'http.request.method') = ?)) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? GROUP BY `service.name` ORDER BY __result_0 DESC LIMIT ?) SELECT toStartOfInterval(timestamp, INTERVAL 30 SECOND) AS ts, toString(multiIf(mapContains(resources_string, 'service.name') = ?, resources_string['service.name'], NULL)) AS `service.name`, count() AS __result_0 FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND ((resources_string['service.name'] = ? AND mapContains(resources_string, 'service.name') = ?) OR (attributes_string['http.request.method'] = ? AND mapContains(attributes_string, 'http.request.method') = ?)) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? AND (`service.name`) GLOBAL IN (SELECT `service.name` FROM __limit_cte) GROUP BY ts, `service.name`", - Args: []any{"redis-manual", "%service.name%", "%service.name\":\"redis-manual%", uint64(1747945619), uint64(1747983448), true, "redis-manual", true, "GET", true, "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), 10, true, "redis-manual", true, "GET", true, "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448)}, + Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE ((simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) OR true) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?), __limit_cte AS (SELECT toString(multiIf(multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL), NULL)) AS `service.name`, count() AS __result_0 FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND ((multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) = ? AND multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?) OR (attributes_string['http.request.method'] = ? AND mapContains(attributes_string, 'http.request.method') = ?)) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? GROUP BY `service.name` ORDER BY __result_0 DESC LIMIT ?) SELECT toStartOfInterval(timestamp, INTERVAL 30 SECOND) AS ts, toString(multiIf(multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL), NULL)) AS `service.name`, count() AS __result_0 FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND ((multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) = ? AND multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?) OR (attributes_string['http.request.method'] = ? AND mapContains(attributes_string, 'http.request.method') = ?)) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? AND (`service.name`) GLOBAL IN (SELECT `service.name` FROM __limit_cte) GROUP BY ts, `service.name`", + Args: []any{"redis-manual", "%service.name%", "%service.name\":\"redis-manual%", uint64(1747945619), uint64(1747983448), "NULL", "redis-manual", "NULL", "GET", true, "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), 10, "NULL", "redis-manual", "NULL", "GET", true, "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448)}, }, expectedErr: nil, }, @@ -187,8 +187,8 @@ func TestStatementBuilder(t *testing.T) { }, }, expected: qbtypes.Statement{ - Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?), __limit_cte AS (SELECT toString(multiIf(mapContains(resources_string, 'service.name') = ?, resources_string['service.name'], NULL)) AS `service.name`, sum(multiIf(mapContains(attributes_number, 'metric.max_count') = ?, toFloat64(attributes_number['metric.max_count']), NULL)) AS __result_0 FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? GROUP BY `service.name` ORDER BY __result_0 DESC LIMIT ?) SELECT toStartOfInterval(timestamp, INTERVAL 30 SECOND) AS ts, toString(multiIf(mapContains(resources_string, 'service.name') = ?, resources_string['service.name'], NULL)) AS `service.name`, sum(multiIf(mapContains(attributes_number, 'metric.max_count') = ?, toFloat64(attributes_number['metric.max_count']), NULL)) AS __result_0 FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? AND (`service.name`) GLOBAL IN (SELECT `service.name` FROM __limit_cte) GROUP BY ts, `service.name`", - Args: []any{"redis-manual", "%service.name%", "%service.name\":\"redis-manual%", uint64(1747945619), uint64(1747983448), true, true, "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), 10, true, true, "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448)}, + Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?), __limit_cte AS (SELECT toString(multiIf(multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL), NULL)) AS `service.name`, sum(multiIf(mapContains(attributes_number, 'metric.max_count') = ?, toFloat64(attributes_number['metric.max_count']), NULL)) AS __result_0 FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? GROUP BY `service.name` ORDER BY __result_0 DESC LIMIT ?) SELECT toStartOfInterval(timestamp, INTERVAL 30 SECOND) AS ts, toString(multiIf(multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL), NULL)) AS `service.name`, sum(multiIf(mapContains(attributes_number, 'metric.max_count') = ?, toFloat64(attributes_number['metric.max_count']), NULL)) AS __result_0 FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? AND (`service.name`) GLOBAL IN (SELECT `service.name` FROM __limit_cte) GROUP BY ts, `service.name`", + Args: []any{"redis-manual", "%service.name%", "%service.name\":\"redis-manual%", uint64(1747945619), uint64(1747983448), "NULL", true, "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), 10, "NULL", true, "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448)}, }, expectedErr: nil, }, @@ -216,8 +216,8 @@ func TestStatementBuilder(t *testing.T) { }, }, expected: qbtypes.Statement{ - Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?), __limit_cte AS (SELECT toString(multiIf(mapContains(resources_string, 'service.name') = ?, resources_string['service.name'], NULL)) AS `service.name`, sum(multiIf(`attribute_number_cart$$items_count_exists` = ?, toFloat64(`attribute_number_cart$$items_count`), NULL)) AS __result_0 FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? GROUP BY `service.name` ORDER BY __result_0 DESC LIMIT ?) SELECT toStartOfInterval(timestamp, INTERVAL 30 SECOND) AS ts, toString(multiIf(mapContains(resources_string, 'service.name') = ?, resources_string['service.name'], NULL)) AS `service.name`, sum(multiIf(`attribute_number_cart$$items_count_exists` = ?, toFloat64(`attribute_number_cart$$items_count`), NULL)) AS __result_0 FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? AND (`service.name`) GLOBAL IN (SELECT `service.name` FROM __limit_cte) GROUP BY ts, `service.name`", - Args: []any{"redis-manual", "%service.name%", "%service.name\":\"redis-manual%", uint64(1747945619), uint64(1747983448), true, true, "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), 10, true, true, "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448)}, + Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?), __limit_cte AS (SELECT toString(multiIf(multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL), NULL)) AS `service.name`, sum(multiIf(`attribute_number_cart$$items_count_exists` = ?, toFloat64(`attribute_number_cart$$items_count`), NULL)) AS __result_0 FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? GROUP BY `service.name` ORDER BY __result_0 DESC LIMIT ?) SELECT toStartOfInterval(timestamp, INTERVAL 30 SECOND) AS ts, toString(multiIf(multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL), NULL)) AS `service.name`, sum(multiIf(`attribute_number_cart$$items_count_exists` = ?, toFloat64(`attribute_number_cart$$items_count`), NULL)) AS __result_0 FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? AND (`service.name`) GLOBAL IN (SELECT `service.name` FROM __limit_cte) GROUP BY ts, `service.name`", + Args: []any{"redis-manual", "%service.name%", "%service.name\":\"redis-manual%", uint64(1747945619), uint64(1747983448), "NULL", true, "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), 10, "NULL", true, "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448)}, }, expectedErr: nil, }, @@ -255,8 +255,8 @@ func TestStatementBuilder(t *testing.T) { }, }, expected: qbtypes.Statement{ - Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?), __limit_cte AS (SELECT toString(multiIf(mapContains(resources_string, 'service.name') = ?, resources_string['service.name'], NULL)) AS `service.name`, sum(multiIf(`attribute_number_cart$$items_count_exists` = ?, toFloat64(`attribute_number_cart$$items_count`), NULL)) AS __result_0 FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? GROUP BY `service.name` ORDER BY `service.name` desc LIMIT ?) SELECT toStartOfInterval(timestamp, INTERVAL 30 SECOND) AS ts, toString(multiIf(mapContains(resources_string, 'service.name') = ?, resources_string['service.name'], NULL)) AS `service.name`, sum(multiIf(`attribute_number_cart$$items_count_exists` = ?, toFloat64(`attribute_number_cart$$items_count`), NULL)) AS __result_0 FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? AND (`service.name`) GLOBAL IN (SELECT `service.name` FROM __limit_cte) GROUP BY ts, `service.name` ORDER BY `service.name` desc, ts desc", - Args: []any{"redis-manual", "%service.name%", "%service.name\":\"redis-manual%", uint64(1747945619), uint64(1747983448), true, true, "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), 10, true, true, "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448)}, + Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?), __limit_cte AS (SELECT toString(multiIf(multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL), NULL)) AS `service.name`, sum(multiIf(`attribute_number_cart$$items_count_exists` = ?, toFloat64(`attribute_number_cart$$items_count`), NULL)) AS __result_0 FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? GROUP BY `service.name` ORDER BY `service.name` desc LIMIT ?) SELECT toStartOfInterval(timestamp, INTERVAL 30 SECOND) AS ts, toString(multiIf(multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL), NULL)) AS `service.name`, sum(multiIf(`attribute_number_cart$$items_count_exists` = ?, toFloat64(`attribute_number_cart$$items_count`), NULL)) AS __result_0 FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? AND (`service.name`) GLOBAL IN (SELECT `service.name` FROM __limit_cte) GROUP BY ts, `service.name` ORDER BY `service.name` desc, ts desc", + Args: []any{"redis-manual", "%service.name%", "%service.name\":\"redis-manual%", uint64(1747945619), uint64(1747983448), "NULL", true, "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), 10, "NULL", true, "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448)}, }, expectedErr: nil, }, @@ -412,7 +412,7 @@ func TestStatementBuilderListQuery(t *testing.T) { }, }, expected: qbtypes.Statement{ - Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?) SELECT name AS `name`, resources_string['service.name'] AS `service.name`, duration_nano AS `duration_nano`, `attribute_number_cart$$items_count` AS `cart.items_count`, timestamp AS `timestamp`, span_id AS `span_id`, trace_id AS `trace_id` FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? LIMIT ?", + Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?) SELECT name AS `name`, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) AS `service.name`, duration_nano AS `duration_nano`, `attribute_number_cart$$items_count` AS `cart.items_count`, timestamp AS `timestamp`, span_id AS `span_id`, trace_id AS `trace_id` FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? LIMIT ?", Args: []any{"redis-manual", "%service.name%", "%service.name\":\"redis-manual%", uint64(1747945619), uint64(1747983448), "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), 10}, }, expectedErr: nil, @@ -441,7 +441,7 @@ func TestStatementBuilderListQuery(t *testing.T) { Limit: 10, }, expected: qbtypes.Statement{ - Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?) SELECT duration_nano AS `duration_nano`, name AS `name`, response_status_code AS `response_status_code`, `resource_string_service$$name` AS `service.name`, span_id AS `span_id`, timestamp AS `timestamp`, trace_id AS `trace_id` FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? ORDER BY attributes_string['user.id'] AS `user.id` desc LIMIT ?", + Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?) SELECT duration_nano AS `duration_nano`, name AS `name`, response_status_code AS `response_status_code`, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, `resource_string_service$$name_exists`==true, `resource_string_service$$name`, NULL) AS `service.name`, span_id AS `span_id`, timestamp AS `timestamp`, trace_id AS `trace_id` FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? ORDER BY attributes_string['user.id'] AS `user.id` desc LIMIT ?", Args: []any{"redis-manual", "%service.name%", "%service.name\":\"redis-manual%", uint64(1747945619), uint64(1747983448), "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), 10}, }, expectedErr: nil, diff --git a/pkg/telemetrytraces/trace_operator_cte_builder_test.go b/pkg/telemetrytraces/trace_operator_cte_builder_test.go index 420f2c10fe2c..fa8f9b40b4a9 100644 --- a/pkg/telemetrytraces/trace_operator_cte_builder_test.go +++ b/pkg/telemetrytraces/trace_operator_cte_builder_test.go @@ -66,7 +66,7 @@ func TestTraceOperatorStatementBuilder(t *testing.T) { }, }, expected: qbtypes.Statement{ - Query: "WITH toDateTime64(1747947419000000000, 9) AS t_from, toDateTime64(1747983448000000000, 9) AS t_to, 1747945619 AS bucket_from, 1747983448 AS bucket_to, all_spans AS (SELECT *, resource_string_service$$name AS `service.name` FROM signoz_traces.distributed_signoz_index_v3 WHERE timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), __resource_filter_A AS (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?), A AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_A) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? AND true), __resource_filter_B AS (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?), B AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_B) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? AND true), A_DIR_DESC_B AS (SELECT p.* FROM A AS p INNER JOIN B AS c ON p.trace_id = c.trace_id AND p.span_id = c.parent_span_id) SELECT timestamp, trace_id, span_id, name, duration_nano, parent_span_id, resources_string['service.name'] AS `service.name` FROM A_DIR_DESC_B ORDER BY timestamp DESC LIMIT ? SETTINGS distributed_product_mode='allow', max_memory_usage=10000000000", + Query: "WITH toDateTime64(1747947419000000000, 9) AS t_from, toDateTime64(1747983448000000000, 9) AS t_to, 1747945619 AS bucket_from, 1747983448 AS bucket_to, all_spans AS (SELECT *, resource_string_service$$name AS `service.name` FROM signoz_traces.distributed_signoz_index_v3 WHERE timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), __resource_filter_A AS (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?), A AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_A) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? AND true), __resource_filter_B AS (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?), B AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_B) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? AND true), A_DIR_DESC_B AS (SELECT p.* FROM A AS p INNER JOIN B AS c ON p.trace_id = c.trace_id AND p.span_id = c.parent_span_id) SELECT timestamp, trace_id, span_id, name, duration_nano, parent_span_id, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) AS `service.name` FROM A_DIR_DESC_B ORDER BY timestamp DESC LIMIT ? SETTINGS distributed_product_mode='allow', max_memory_usage=10000000000", Args: []any{"1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), "frontend", "%service.name%", "%service.name\":\"frontend%", uint64(1747945619), uint64(1747983448), "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), "backend", "%service.name%", "%service.name\":\"backend%", uint64(1747945619), uint64(1747983448), "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), 10}, }, expectedErr: nil, @@ -263,8 +263,8 @@ func TestTraceOperatorStatementBuilder(t *testing.T) { }, }, expected: qbtypes.Statement{ - Query: "WITH toDateTime64(1747947419000000000, 9) AS t_from, toDateTime64(1747983448000000000, 9) AS t_to, 1747945619 AS bucket_from, 1747983448 AS bucket_to, all_spans AS (SELECT *, resource_string_service$$name AS `service.name` FROM signoz_traces.distributed_signoz_index_v3 WHERE timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), __resource_filter_A AS (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?), A AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_A) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? AND true), __resource_filter_B AS (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?), B AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_B) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? AND true), A_DIR_DESC_B AS (SELECT p.* FROM A AS p INNER JOIN B AS c ON p.trace_id = c.trace_id AND p.span_id = c.parent_span_id) SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, toString(multiIf(mapContains(resources_string, 'service.name') = ?, resources_string['service.name'], NULL)) AS `service.name`, count() AS __result_0 FROM A_DIR_DESC_B GROUP BY ts, `service.name` ORDER BY ts desc SETTINGS distributed_product_mode='allow', max_memory_usage=10000000000", - Args: []any{"1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), "frontend", "%service.name%", "%service.name\":\"frontend%", uint64(1747945619), uint64(1747983448), "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), "backend", "%service.name%", "%service.name\":\"backend%", uint64(1747945619), uint64(1747983448), "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), true}, + Query: "WITH toDateTime64(1747947419000000000, 9) AS t_from, toDateTime64(1747983448000000000, 9) AS t_to, 1747945619 AS bucket_from, 1747983448 AS bucket_to, all_spans AS (SELECT *, resource_string_service$$name AS `service.name` FROM signoz_traces.distributed_signoz_index_v3 WHERE timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), __resource_filter_A AS (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?), A AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_A) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? AND true), __resource_filter_B AS (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?), B AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_B) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? AND true), A_DIR_DESC_B AS (SELECT p.* FROM A AS p INNER JOIN B AS c ON p.trace_id = c.trace_id AND p.span_id = c.parent_span_id) SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, toString(multiIf(multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL), NULL)) AS `service.name`, count() AS __result_0 FROM A_DIR_DESC_B GROUP BY ts, `service.name` ORDER BY ts desc SETTINGS distributed_product_mode='allow', max_memory_usage=10000000000", + Args: []any{"1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), "frontend", "%service.name%", "%service.name\":\"frontend%", uint64(1747945619), uint64(1747983448), "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), "backend", "%service.name%", "%service.name\":\"backend%", uint64(1747945619), uint64(1747983448), "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), "NULL"}, }, expectedErr: nil, }, @@ -322,8 +322,8 @@ func TestTraceOperatorStatementBuilder(t *testing.T) { }, }, expected: qbtypes.Statement{ - Query: "WITH toDateTime64(1747947419000000000, 9) AS t_from, toDateTime64(1747983448000000000, 9) AS t_to, 1747945619 AS bucket_from, 1747983448 AS bucket_to, all_spans AS (SELECT *, resource_string_service$$name AS `service.name` FROM signoz_traces.distributed_signoz_index_v3 WHERE timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), __resource_filter_A AS (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?), A AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_A) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? AND true), __resource_filter_B AS (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE true AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?), B AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_B) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? AND toFloat64(response_status_code) < ?), A_AND_B AS (SELECT l.* FROM A AS l INNER JOIN B AS r ON l.trace_id = r.trace_id) SELECT toString(multiIf(mapContains(resources_string, 'service.name') = ?, resources_string['service.name'], NULL)) AS `service.name`, avg(multiIf(duration_nano <> ?, duration_nano, NULL)) AS __result_0 FROM A_AND_B GROUP BY `service.name` ORDER BY __result_0 desc SETTINGS distributed_product_mode='allow', max_memory_usage=10000000000", - Args: []any{"1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), "frontend", "%service.name%", "%service.name\":\"frontend%", uint64(1747945619), uint64(1747983448), "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), uint64(1747945619), uint64(1747983448), "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), float64(400), true, 0}, + Query: "WITH toDateTime64(1747947419000000000, 9) AS t_from, toDateTime64(1747983448000000000, 9) AS t_to, 1747945619 AS bucket_from, 1747983448 AS bucket_to, all_spans AS (SELECT *, resource_string_service$$name AS `service.name` FROM signoz_traces.distributed_signoz_index_v3 WHERE timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), __resource_filter_A AS (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?), A AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_A) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? AND true), __resource_filter_B AS (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE true AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?), B AS (SELECT * FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter_B) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? AND toFloat64(response_status_code) < ?), A_AND_B AS (SELECT l.* FROM A AS l INNER JOIN B AS r ON l.trace_id = r.trace_id) SELECT toString(multiIf(multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL) <> ?, multiIf(resource.`service.name` IS NOT NULL, resource.`service.name`::String, mapContains(resources_string, 'service.name'), resources_string['service.name'], NULL), NULL)) AS `service.name`, avg(multiIf(duration_nano <> ?, duration_nano, NULL)) AS __result_0 FROM A_AND_B GROUP BY `service.name` ORDER BY __result_0 desc SETTINGS distributed_product_mode='allow', max_memory_usage=10000000000", + Args: []any{"1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), "frontend", "%service.name%", "%service.name\":\"frontend%", uint64(1747945619), uint64(1747983448), "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), uint64(1747945619), uint64(1747983448), "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), float64(400), "NULL", 0}, }, expectedErr: nil, }, From a6869418804202e375b4f73960a79ed857d883c7 Mon Sep 17 00:00:00 2001 From: Nityananda Gohain Date: Sun, 14 Sep 2025 18:30:16 +0530 Subject: [PATCH 3/9] fix: exception on resource filters with numeric values (#9028) --- .../resourcefilter/condition_builder.go | 59 ++++++++----------- .../resourcefilter/condition_builder_test.go | 55 +++++++++++++++++ 2 files changed, 81 insertions(+), 33 deletions(-) diff --git a/pkg/querybuilder/resourcefilter/condition_builder.go b/pkg/querybuilder/resourcefilter/condition_builder.go index 66fdefc90ee4..0539b3bfa008 100644 --- a/pkg/querybuilder/resourcefilter/condition_builder.go +++ b/pkg/querybuilder/resourcefilter/condition_builder.go @@ -22,21 +22,20 @@ func NewConditionBuilder(fm qbtypes.FieldMapper) *defaultConditionBuilder { func valueForIndexFilter(op qbtypes.FilterOperator, key *telemetrytypes.TelemetryFieldKey, value any) any { switch v := value.(type) { - case string: - if op == qbtypes.FilterOperatorEqual || op == qbtypes.FilterOperatorNotEqual { - return fmt.Sprintf(`%%%s":"%s%%`, key.Name, v) - } - return fmt.Sprintf(`%%%s%%%s%%`, key.Name, v) case []any: // assuming array will always be for in and not in values := make([]string, 0, len(v)) for _, v := range v { - values = append(values, fmt.Sprintf(`%%%s":"%s%%`, key.Name, v)) + values = append(values, fmt.Sprintf(`%%%s":"%s%%`, key.Name, querybuilder.FormatValueForContains(v))) } return values + default: + // format to string for anything else as we store resource values as string + if op == qbtypes.FilterOperatorEqual || op == qbtypes.FilterOperatorNotEqual { + return fmt.Sprintf(`%%%s":"%s%%`, key.Name, querybuilder.FormatValueForContains(v)) + } + return fmt.Sprintf(`%%%s%%%s%%`, key.Name, querybuilder.FormatValueForContains(v)) } - // resource table expects string value - return fmt.Sprintf(`%%%v%%`, value) } func keyIndexFilter(key *telemetrytypes.TelemetryFieldKey) any { @@ -55,15 +54,9 @@ func (b *defaultConditionBuilder) ConditionFor( return "true", nil } - switch op { - case qbtypes.FilterOperatorContains, - qbtypes.FilterOperatorNotContains, - qbtypes.FilterOperatorILike, - qbtypes.FilterOperatorNotILike, - qbtypes.FilterOperatorLike, - qbtypes.FilterOperatorNotLike: - value = querybuilder.FormatValueForContains(value) - } + // except for in, not in, between, not between all other operators should have formatted value + // as we store resource values as string + formattedValue := querybuilder.FormatValueForContains(value) column, err := b.fm.ColumnFor(ctx, key) if err != nil { @@ -81,34 +74,34 @@ func (b *defaultConditionBuilder) ConditionFor( switch op { case qbtypes.FilterOperatorEqual: return sb.And( - sb.E(fieldName, value), + sb.E(fieldName, formattedValue), keyIdxFilter, sb.Like(column.Name, valueForIndexFilter), ), nil case qbtypes.FilterOperatorNotEqual: return sb.And( - sb.NE(fieldName, value), + sb.NE(fieldName, formattedValue), sb.NotLike(column.Name, valueForIndexFilter), ), nil case qbtypes.FilterOperatorGreaterThan: - return sb.And(sb.GT(fieldName, value), keyIdxFilter), nil + return sb.And(sb.GT(fieldName, formattedValue), keyIdxFilter), nil case qbtypes.FilterOperatorGreaterThanOrEq: - return sb.And(sb.GE(fieldName, value), keyIdxFilter), nil + return sb.And(sb.GE(fieldName, formattedValue), keyIdxFilter), nil case qbtypes.FilterOperatorLessThan: - return sb.And(sb.LT(fieldName, value), keyIdxFilter), nil + return sb.And(sb.LT(fieldName, formattedValue), keyIdxFilter), nil case qbtypes.FilterOperatorLessThanOrEq: - return sb.And(sb.LE(fieldName, value), keyIdxFilter), nil + return sb.And(sb.LE(fieldName, formattedValue), keyIdxFilter), nil case qbtypes.FilterOperatorLike, qbtypes.FilterOperatorILike: return sb.And( - sb.ILike(fieldName, value), + sb.ILike(fieldName, formattedValue), keyIdxFilter, sb.ILike(column.Name, valueForIndexFilter), ), nil case qbtypes.FilterOperatorNotLike, qbtypes.FilterOperatorNotILike: // no index filter: as cannot apply `not contains x%y` as y can be somewhere else return sb.And( - sb.NotILike(fieldName, value), + sb.NotILike(fieldName, formattedValue), ), nil case qbtypes.FilterOperatorBetween: @@ -119,7 +112,7 @@ func (b *defaultConditionBuilder) ConditionFor( if len(values) != 2 { return "", qbtypes.ErrBetweenValues } - return sb.And(keyIdxFilter, sb.Between(fieldName, values[0], values[1])), nil + return sb.And(keyIdxFilter, sb.Between(fieldName, querybuilder.FormatValueForContains(values[0]), querybuilder.FormatValueForContains(values[1]))), nil case qbtypes.FilterOperatorNotBetween: values, ok := value.([]any) if !ok { @@ -128,7 +121,7 @@ func (b *defaultConditionBuilder) ConditionFor( if len(values) != 2 { return "", qbtypes.ErrBetweenValues } - return sb.And(sb.NotBetween(fieldName, values[0], values[1])), nil + return sb.And(sb.NotBetween(fieldName, querybuilder.FormatValueForContains(values[0]), querybuilder.FormatValueForContains(values[1]))), nil case qbtypes.FilterOperatorIn: values, ok := value.([]any) @@ -137,7 +130,7 @@ func (b *defaultConditionBuilder) ConditionFor( } inConditions := make([]string, 0, len(values)) for _, v := range values { - inConditions = append(inConditions, sb.E(fieldName, v)) + inConditions = append(inConditions, sb.E(fieldName, querybuilder.FormatValueForContains(v))) } mainCondition := sb.Or(inConditions...) valConditions := make([]string, 0, len(values)) @@ -156,7 +149,7 @@ func (b *defaultConditionBuilder) ConditionFor( } notInConditions := make([]string, 0, len(values)) for _, v := range values { - notInConditions = append(notInConditions, sb.NE(fieldName, v)) + notInConditions = append(notInConditions, sb.NE(fieldName, querybuilder.FormatValueForContains(v))) } mainCondition := sb.And(notInConditions...) valConditions := make([]string, 0, len(values)) @@ -180,24 +173,24 @@ func (b *defaultConditionBuilder) ConditionFor( case qbtypes.FilterOperatorRegexp: return sb.And( - fmt.Sprintf("match(%s, %s)", fieldName, sb.Var(value)), + fmt.Sprintf("match(%s, %s)", fieldName, sb.Var(formattedValue)), keyIdxFilter, ), nil case qbtypes.FilterOperatorNotRegexp: return sb.And( - fmt.Sprintf("NOT match(%s, %s)", fieldName, sb.Var(value)), + fmt.Sprintf("NOT match(%s, %s)", fieldName, sb.Var(formattedValue)), ), nil case qbtypes.FilterOperatorContains: return sb.And( - sb.ILike(fieldName, fmt.Sprintf(`%%%s%%`, value)), + sb.ILike(fieldName, fmt.Sprintf(`%%%s%%`, formattedValue)), keyIdxFilter, sb.ILike(column.Name, valueForIndexFilter), ), nil case qbtypes.FilterOperatorNotContains: // no index filter: as cannot apply `not contains x%y` as y can be somewhere else return sb.And( - sb.NotILike(fieldName, fmt.Sprintf(`%%%s%%`, value)), + sb.NotILike(fieldName, fmt.Sprintf(`%%%s%%`, formattedValue)), ), nil } return "", qbtypes.ErrUnsupportedOperator diff --git a/pkg/querybuilder/resourcefilter/condition_builder_test.go b/pkg/querybuilder/resourcefilter/condition_builder_test.go index ac4cbab7bf66..6a9363f595dd 100644 --- a/pkg/querybuilder/resourcefilter/condition_builder_test.go +++ b/pkg/querybuilder/resourcefilter/condition_builder_test.go @@ -143,6 +143,61 @@ func TestConditionBuilder(t *testing.T) { expected: "simpleJSONHas(labels, 'k8s.namespace.name') <> ?", expectedArgs: []any{true}, }, + { + name: "number_equals", + key: &telemetrytypes.TelemetryFieldKey{ + Name: "test_num", + FieldContext: telemetrytypes.FieldContextResource, + }, + op: querybuildertypesv5.FilterOperatorEqual, + value: 1, + expected: "simpleJSONExtractString(labels, 'test_num') = ? AND labels LIKE ? AND labels LIKE ?", + expectedArgs: []any{"1", "%test_num%", "%test_num\":\"1%"}, + }, + { + name: "number_gt", + key: &telemetrytypes.TelemetryFieldKey{ + Name: "test_num", + FieldContext: telemetrytypes.FieldContextResource, + }, + op: querybuildertypesv5.FilterOperatorGreaterThan, + value: 1, + expected: "simpleJSONExtractString(labels, 'test_num') > ? AND labels LIKE ?", + expectedArgs: []any{"1", "%test_num%"}, + }, + { + name: "number_in", + key: &telemetrytypes.TelemetryFieldKey{ + Name: "test_num", + FieldContext: telemetrytypes.FieldContextResource, + }, + op: querybuildertypesv5.FilterOperatorIn, + value: []any{1, 2}, + expected: "(simpleJSONExtractString(labels, 'test_num') = ? OR simpleJSONExtractString(labels, 'test_num') = ?) AND labels LIKE ? AND (labels LIKE ? OR labels LIKE ?)", + expectedArgs: []any{"1", "2", "%test_num%", "%test_num\":\"1%", "%test_num\":\"2%"}, + }, + { + name: "number_between", + key: &telemetrytypes.TelemetryFieldKey{ + Name: "test_num", + FieldContext: telemetrytypes.FieldContextResource, + }, + op: querybuildertypesv5.FilterOperatorBetween, + value: []any{1, 2}, + expected: "labels LIKE ? AND simpleJSONExtractString(labels, 'test_num') BETWEEN ? AND ?", + expectedArgs: []any{"%test_num%", "1", "2"}, + }, + { + name: "string_regexp", + key: &telemetrytypes.TelemetryFieldKey{ + Name: "k8s.namespace.name", + FieldContext: telemetrytypes.FieldContextResource, + }, + op: querybuildertypesv5.FilterOperatorRegexp, + value: "ban.*", + expected: "match(simpleJSONExtractString(labels, 'k8s.namespace.name'), ?) AND labels LIKE ?", + expectedArgs: []any{"ban.*", "%k8s.namespace.name%"}, + }, } fm := NewFieldMapper() From 38ca467d13ec645ba1ef6201369270b00b9f9cec Mon Sep 17 00:00:00 2001 From: Nityananda Gohain Date: Sun, 14 Sep 2025 18:42:48 +0530 Subject: [PATCH 4/9] fix: trace perf - scan only required traces (#9072) --- pkg/telemetrytraces/statement_builder.go | 1 + pkg/telemetrytraces/stmt_builder_test.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/telemetrytraces/statement_builder.go b/pkg/telemetrytraces/statement_builder.go index b5c1d3d1297f..beb243cdba59 100644 --- a/pkg/telemetrytraces/statement_builder.go +++ b/pkg/telemetrytraces/statement_builder.go @@ -390,6 +390,7 @@ func (b *traceQueryStatementBuilder) buildTraceQuery( innerSB.Select("trace_id", "duration_nano", sqlbuilder.Escape("resource_string_service$$name as `service.name`"), "name") innerSB.From(fmt.Sprintf("%s.%s", DBName, SpanIndexV3TableName)) innerSB.Where("parent_span_id = ''") + innerSB.Where("trace_id GLOBAL IN __toe") // Add time filter to inner query innerSB.Where( diff --git a/pkg/telemetrytraces/stmt_builder_test.go b/pkg/telemetrytraces/stmt_builder_test.go index ba089266f34b..96dc2ba7a335 100644 --- a/pkg/telemetrytraces/stmt_builder_test.go +++ b/pkg/telemetrytraces/stmt_builder_test.go @@ -547,7 +547,7 @@ func TestStatementBuilderTraceQuery(t *testing.T) { Limit: 10, }, expected: qbtypes.Statement{ - Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?), __toe AS (SELECT trace_id FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), __toe_duration_sorted AS (SELECT trace_id, duration_nano, resource_string_service$$name as `service.name`, name FROM signoz_traces.distributed_signoz_index_v3 WHERE parent_span_id = '' AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? ORDER BY duration_nano DESC LIMIT 1 BY trace_id) SELECT __toe_duration_sorted.`service.name` AS `service.name`, __toe_duration_sorted.name AS `name`, count() AS span_count, __toe_duration_sorted.duration_nano AS `duration_nano`, __toe_duration_sorted.trace_id AS `trace_id` FROM __toe INNER JOIN __toe_duration_sorted ON __toe.trace_id = __toe_duration_sorted.trace_id GROUP BY trace_id, duration_nano, name, `service.name` ORDER BY duration_nano DESC LIMIT 1 BY trace_id LIMIT ? SETTINGS distributed_product_mode='allow', max_memory_usage=10000000000", + Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (simpleJSONExtractString(labels, 'service.name') = ? AND labels LIKE ? AND labels LIKE ?) AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?), __toe AS (SELECT trace_id FROM signoz_traces.distributed_signoz_index_v3 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND true AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ?), __toe_duration_sorted AS (SELECT trace_id, duration_nano, resource_string_service$$name as `service.name`, name FROM signoz_traces.distributed_signoz_index_v3 WHERE parent_span_id = '' AND trace_id GLOBAL IN __toe AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? ORDER BY duration_nano DESC LIMIT 1 BY trace_id) SELECT __toe_duration_sorted.`service.name` AS `service.name`, __toe_duration_sorted.name AS `name`, count() AS span_count, __toe_duration_sorted.duration_nano AS `duration_nano`, __toe_duration_sorted.trace_id AS `trace_id` FROM __toe INNER JOIN __toe_duration_sorted ON __toe.trace_id = __toe_duration_sorted.trace_id GROUP BY trace_id, duration_nano, name, `service.name` ORDER BY duration_nano DESC LIMIT 1 BY trace_id LIMIT ? SETTINGS distributed_product_mode='allow', max_memory_usage=10000000000", Args: []any{"redis-manual", "%service.name%", "%service.name\":\"redis-manual%", uint64(1747945619), uint64(1747983448), "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), 10}, }, expectedErr: nil, From 252786deb6c7eeaa89a184d3eaddedb664930a1b Mon Sep 17 00:00:00 2001 From: Srikanth Chekuri Date: Sun, 14 Sep 2025 20:41:13 +0530 Subject: [PATCH 5/9] chore: make qb v5 default (#9085) --- pkg/modules/dashboard/impldashboard/handler.go | 9 +++------ pkg/querybuilder/init.go | 14 -------------- 2 files changed, 3 insertions(+), 20 deletions(-) diff --git a/pkg/modules/dashboard/impldashboard/handler.go b/pkg/modules/dashboard/impldashboard/handler.go index 39a5987a40c5..139f4c3a6a82 100644 --- a/pkg/modules/dashboard/impldashboard/handler.go +++ b/pkg/modules/dashboard/impldashboard/handler.go @@ -10,7 +10,6 @@ import ( "github.com/SigNoz/signoz/pkg/factory" "github.com/SigNoz/signoz/pkg/http/render" "github.com/SigNoz/signoz/pkg/modules/dashboard" - "github.com/SigNoz/signoz/pkg/querybuilder" "github.com/SigNoz/signoz/pkg/transition" "github.com/SigNoz/signoz/pkg/types/authtypes" "github.com/SigNoz/signoz/pkg/types/ctxtypes" @@ -51,11 +50,9 @@ func (handler *handler) Create(rw http.ResponseWriter, r *http.Request) { return } - if querybuilder.QBV5Enabled { - dashboardMigrator := transition.NewDashboardMigrateV5(handler.providerSettings.Logger, nil, nil) - if req["version"] != "v5" { - dashboardMigrator.Migrate(ctx, req) - } + dashboardMigrator := transition.NewDashboardMigrateV5(handler.providerSettings.Logger, nil, nil) + if req["version"] != "v5" { + dashboardMigrator.Migrate(ctx, req) } dashboard, err := handler.module.Create(ctx, orgID, claims.Email, valuer.MustNewUUID(claims.UserID), req) diff --git a/pkg/querybuilder/init.go b/pkg/querybuilder/init.go index dc481163e226..4fef9bfb026f 100644 --- a/pkg/querybuilder/init.go +++ b/pkg/querybuilder/init.go @@ -1,15 +1 @@ package querybuilder - -import ( - "os" - "strings" -) - -var QBV5Enabled = false - -func init() { - v := os.Getenv("ENABLE_QB_V5") - if strings.ToLower(v) == "true" || strings.ToLower(v) == "1" { - QBV5Enabled = true - } -} From c982b1e76d3f165b4b4081143bd86387ce6a2dfb Mon Sep 17 00:00:00 2001 From: Srikanth Chekuri Date: Sun, 14 Sep 2025 22:14:42 +0530 Subject: [PATCH 6/9] chore: allow number segment, #, @, {} in key (#9082) --- frontend/src/parser/FilterQueryLexer.interp | 2 +- frontend/src/parser/FilterQueryLexer.ts | 183 ++++++------- frontend/src/parser/FilterQueryListener.ts | 2 +- frontend/src/parser/FilterQueryParser.ts | 2 +- frontend/src/parser/FilterQueryVisitor.ts | 2 +- grammar/FilterQuery.g4 | 4 +- pkg/parser/grammar/FilterQueryLexer.interp | 2 +- pkg/parser/grammar/filterquery_lexer.go | 247 +++++++++--------- pkg/telemetrylogs/filter_expr_logs_test.go | 40 +++ pkg/telemetrylogs/test_data.go | 28 ++ .../querybuildertypesv5/builder_elements.go | 1 + scripts/grammar/generate-frontend-parser.sh | 11 + 12 files changed, 305 insertions(+), 219 deletions(-) create mode 100755 scripts/grammar/generate-frontend-parser.sh diff --git a/frontend/src/parser/FilterQueryLexer.interp b/frontend/src/parser/FilterQueryLexer.interp index 6f3d2d7e1697..bb681c909134 100644 --- a/frontend/src/parser/FilterQueryLexer.interp +++ b/frontend/src/parser/FilterQueryLexer.interp @@ -115,4 +115,4 @@ mode names: DEFAULT_MODE atn: -[4, 0, 32, 314, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, 1, 0, 1, 0, 1, 1, 1, 1, 1, 2, 1, 2, 1, 3, 1, 3, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 3, 5, 89, 8, 5, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 3, 15, 132, 8, 15, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 3, 17, 149, 8, 17, 1, 18, 1, 18, 1, 18, 1, 19, 1, 19, 1, 19, 1, 19, 1, 20, 1, 20, 1, 20, 1, 20, 1, 21, 1, 21, 1, 21, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 23, 1, 23, 1, 23, 1, 23, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 3, 26, 201, 8, 26, 1, 27, 1, 27, 1, 28, 3, 28, 206, 8, 28, 1, 28, 4, 28, 209, 8, 28, 11, 28, 12, 28, 210, 1, 28, 1, 28, 5, 28, 215, 8, 28, 10, 28, 12, 28, 218, 9, 28, 3, 28, 220, 8, 28, 1, 28, 1, 28, 3, 28, 224, 8, 28, 1, 28, 4, 28, 227, 8, 28, 11, 28, 12, 28, 228, 3, 28, 231, 8, 28, 1, 28, 3, 28, 234, 8, 28, 1, 28, 1, 28, 4, 28, 238, 8, 28, 11, 28, 12, 28, 239, 1, 28, 1, 28, 3, 28, 244, 8, 28, 1, 28, 4, 28, 247, 8, 28, 11, 28, 12, 28, 248, 3, 28, 251, 8, 28, 3, 28, 253, 8, 28, 1, 29, 1, 29, 1, 29, 1, 29, 5, 29, 259, 8, 29, 10, 29, 12, 29, 262, 9, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 5, 29, 269, 8, 29, 10, 29, 12, 29, 272, 9, 29, 1, 29, 3, 29, 275, 8, 29, 1, 30, 1, 30, 5, 30, 279, 8, 30, 10, 30, 12, 30, 282, 9, 30, 1, 31, 1, 31, 1, 31, 1, 32, 1, 32, 1, 32, 1, 32, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 5, 33, 296, 8, 33, 10, 33, 12, 33, 299, 9, 33, 1, 34, 4, 34, 302, 8, 34, 11, 34, 12, 34, 303, 1, 34, 1, 34, 1, 35, 1, 35, 1, 36, 4, 36, 311, 8, 36, 11, 36, 12, 36, 312, 0, 0, 37, 1, 1, 3, 2, 5, 3, 7, 4, 9, 5, 11, 6, 13, 7, 15, 8, 17, 9, 19, 10, 21, 11, 23, 12, 25, 13, 27, 14, 29, 15, 31, 16, 33, 17, 35, 18, 37, 19, 39, 20, 41, 21, 43, 22, 45, 23, 47, 24, 49, 25, 51, 26, 53, 27, 55, 0, 57, 28, 59, 29, 61, 0, 63, 0, 65, 0, 67, 30, 69, 31, 71, 0, 73, 32, 1, 0, 29, 2, 0, 76, 76, 108, 108, 2, 0, 73, 73, 105, 105, 2, 0, 75, 75, 107, 107, 2, 0, 69, 69, 101, 101, 2, 0, 66, 66, 98, 98, 2, 0, 84, 84, 116, 116, 2, 0, 87, 87, 119, 119, 2, 0, 78, 78, 110, 110, 2, 0, 88, 88, 120, 120, 2, 0, 83, 83, 115, 115, 2, 0, 82, 82, 114, 114, 2, 0, 71, 71, 103, 103, 2, 0, 80, 80, 112, 112, 2, 0, 67, 67, 99, 99, 2, 0, 79, 79, 111, 111, 2, 0, 65, 65, 97, 97, 2, 0, 68, 68, 100, 100, 2, 0, 72, 72, 104, 104, 2, 0, 89, 89, 121, 121, 2, 0, 85, 85, 117, 117, 2, 0, 70, 70, 102, 102, 2, 0, 43, 43, 45, 45, 2, 0, 34, 34, 92, 92, 2, 0, 39, 39, 92, 92, 4, 0, 36, 36, 65, 90, 95, 95, 97, 122, 6, 0, 36, 36, 45, 45, 47, 58, 65, 90, 95, 95, 97, 122, 3, 0, 9, 10, 13, 13, 32, 32, 1, 0, 48, 57, 8, 0, 9, 10, 13, 13, 32, 34, 39, 41, 44, 44, 60, 62, 91, 91, 93, 93, 336, 0, 1, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 5, 1, 0, 0, 0, 0, 7, 1, 0, 0, 0, 0, 9, 1, 0, 0, 0, 0, 11, 1, 0, 0, 0, 0, 13, 1, 0, 0, 0, 0, 15, 1, 0, 0, 0, 0, 17, 1, 0, 0, 0, 0, 19, 1, 0, 0, 0, 0, 21, 1, 0, 0, 0, 0, 23, 1, 0, 0, 0, 0, 25, 1, 0, 0, 0, 0, 27, 1, 0, 0, 0, 0, 29, 1, 0, 0, 0, 0, 31, 1, 0, 0, 0, 0, 33, 1, 0, 0, 0, 0, 35, 1, 0, 0, 0, 0, 37, 1, 0, 0, 0, 0, 39, 1, 0, 0, 0, 0, 41, 1, 0, 0, 0, 0, 43, 1, 0, 0, 0, 0, 45, 1, 0, 0, 0, 0, 47, 1, 0, 0, 0, 0, 49, 1, 0, 0, 0, 0, 51, 1, 0, 0, 0, 0, 53, 1, 0, 0, 0, 0, 57, 1, 0, 0, 0, 0, 59, 1, 0, 0, 0, 0, 67, 1, 0, 0, 0, 0, 69, 1, 0, 0, 0, 0, 73, 1, 0, 0, 0, 1, 75, 1, 0, 0, 0, 3, 77, 1, 0, 0, 0, 5, 79, 1, 0, 0, 0, 7, 81, 1, 0, 0, 0, 9, 83, 1, 0, 0, 0, 11, 88, 1, 0, 0, 0, 13, 90, 1, 0, 0, 0, 15, 93, 1, 0, 0, 0, 17, 96, 1, 0, 0, 0, 19, 98, 1, 0, 0, 0, 21, 101, 1, 0, 0, 0, 23, 103, 1, 0, 0, 0, 25, 106, 1, 0, 0, 0, 27, 111, 1, 0, 0, 0, 29, 117, 1, 0, 0, 0, 31, 125, 1, 0, 0, 0, 33, 133, 1, 0, 0, 0, 35, 140, 1, 0, 0, 0, 37, 150, 1, 0, 0, 0, 39, 153, 1, 0, 0, 0, 41, 157, 1, 0, 0, 0, 43, 161, 1, 0, 0, 0, 45, 164, 1, 0, 0, 0, 47, 173, 1, 0, 0, 0, 49, 177, 1, 0, 0, 0, 51, 184, 1, 0, 0, 0, 53, 200, 1, 0, 0, 0, 55, 202, 1, 0, 0, 0, 57, 252, 1, 0, 0, 0, 59, 274, 1, 0, 0, 0, 61, 276, 1, 0, 0, 0, 63, 283, 1, 0, 0, 0, 65, 286, 1, 0, 0, 0, 67, 290, 1, 0, 0, 0, 69, 301, 1, 0, 0, 0, 71, 307, 1, 0, 0, 0, 73, 310, 1, 0, 0, 0, 75, 76, 5, 40, 0, 0, 76, 2, 1, 0, 0, 0, 77, 78, 5, 41, 0, 0, 78, 4, 1, 0, 0, 0, 79, 80, 5, 91, 0, 0, 80, 6, 1, 0, 0, 0, 81, 82, 5, 93, 0, 0, 82, 8, 1, 0, 0, 0, 83, 84, 5, 44, 0, 0, 84, 10, 1, 0, 0, 0, 85, 89, 5, 61, 0, 0, 86, 87, 5, 61, 0, 0, 87, 89, 5, 61, 0, 0, 88, 85, 1, 0, 0, 0, 88, 86, 1, 0, 0, 0, 89, 12, 1, 0, 0, 0, 90, 91, 5, 33, 0, 0, 91, 92, 5, 61, 0, 0, 92, 14, 1, 0, 0, 0, 93, 94, 5, 60, 0, 0, 94, 95, 5, 62, 0, 0, 95, 16, 1, 0, 0, 0, 96, 97, 5, 60, 0, 0, 97, 18, 1, 0, 0, 0, 98, 99, 5, 60, 0, 0, 99, 100, 5, 61, 0, 0, 100, 20, 1, 0, 0, 0, 101, 102, 5, 62, 0, 0, 102, 22, 1, 0, 0, 0, 103, 104, 5, 62, 0, 0, 104, 105, 5, 61, 0, 0, 105, 24, 1, 0, 0, 0, 106, 107, 7, 0, 0, 0, 107, 108, 7, 1, 0, 0, 108, 109, 7, 2, 0, 0, 109, 110, 7, 3, 0, 0, 110, 26, 1, 0, 0, 0, 111, 112, 7, 1, 0, 0, 112, 113, 7, 0, 0, 0, 113, 114, 7, 1, 0, 0, 114, 115, 7, 2, 0, 0, 115, 116, 7, 3, 0, 0, 116, 28, 1, 0, 0, 0, 117, 118, 7, 4, 0, 0, 118, 119, 7, 3, 0, 0, 119, 120, 7, 5, 0, 0, 120, 121, 7, 6, 0, 0, 121, 122, 7, 3, 0, 0, 122, 123, 7, 3, 0, 0, 123, 124, 7, 7, 0, 0, 124, 30, 1, 0, 0, 0, 125, 126, 7, 3, 0, 0, 126, 127, 7, 8, 0, 0, 127, 128, 7, 1, 0, 0, 128, 129, 7, 9, 0, 0, 129, 131, 7, 5, 0, 0, 130, 132, 7, 9, 0, 0, 131, 130, 1, 0, 0, 0, 131, 132, 1, 0, 0, 0, 132, 32, 1, 0, 0, 0, 133, 134, 7, 10, 0, 0, 134, 135, 7, 3, 0, 0, 135, 136, 7, 11, 0, 0, 136, 137, 7, 3, 0, 0, 137, 138, 7, 8, 0, 0, 138, 139, 7, 12, 0, 0, 139, 34, 1, 0, 0, 0, 140, 141, 7, 13, 0, 0, 141, 142, 7, 14, 0, 0, 142, 143, 7, 7, 0, 0, 143, 144, 7, 5, 0, 0, 144, 145, 7, 15, 0, 0, 145, 146, 7, 1, 0, 0, 146, 148, 7, 7, 0, 0, 147, 149, 7, 9, 0, 0, 148, 147, 1, 0, 0, 0, 148, 149, 1, 0, 0, 0, 149, 36, 1, 0, 0, 0, 150, 151, 7, 1, 0, 0, 151, 152, 7, 7, 0, 0, 152, 38, 1, 0, 0, 0, 153, 154, 7, 7, 0, 0, 154, 155, 7, 14, 0, 0, 155, 156, 7, 5, 0, 0, 156, 40, 1, 0, 0, 0, 157, 158, 7, 15, 0, 0, 158, 159, 7, 7, 0, 0, 159, 160, 7, 16, 0, 0, 160, 42, 1, 0, 0, 0, 161, 162, 7, 14, 0, 0, 162, 163, 7, 10, 0, 0, 163, 44, 1, 0, 0, 0, 164, 165, 7, 17, 0, 0, 165, 166, 7, 15, 0, 0, 166, 167, 7, 9, 0, 0, 167, 168, 7, 5, 0, 0, 168, 169, 7, 14, 0, 0, 169, 170, 7, 2, 0, 0, 170, 171, 7, 3, 0, 0, 171, 172, 7, 7, 0, 0, 172, 46, 1, 0, 0, 0, 173, 174, 7, 17, 0, 0, 174, 175, 7, 15, 0, 0, 175, 176, 7, 9, 0, 0, 176, 48, 1, 0, 0, 0, 177, 178, 7, 17, 0, 0, 178, 179, 7, 15, 0, 0, 179, 180, 7, 9, 0, 0, 180, 181, 7, 15, 0, 0, 181, 182, 7, 7, 0, 0, 182, 183, 7, 18, 0, 0, 183, 50, 1, 0, 0, 0, 184, 185, 7, 17, 0, 0, 185, 186, 7, 15, 0, 0, 186, 187, 7, 9, 0, 0, 187, 188, 7, 15, 0, 0, 188, 189, 7, 0, 0, 0, 189, 190, 7, 0, 0, 0, 190, 52, 1, 0, 0, 0, 191, 192, 7, 5, 0, 0, 192, 193, 7, 10, 0, 0, 193, 194, 7, 19, 0, 0, 194, 201, 7, 3, 0, 0, 195, 196, 7, 20, 0, 0, 196, 197, 7, 15, 0, 0, 197, 198, 7, 0, 0, 0, 198, 199, 7, 9, 0, 0, 199, 201, 7, 3, 0, 0, 200, 191, 1, 0, 0, 0, 200, 195, 1, 0, 0, 0, 201, 54, 1, 0, 0, 0, 202, 203, 7, 21, 0, 0, 203, 56, 1, 0, 0, 0, 204, 206, 3, 55, 27, 0, 205, 204, 1, 0, 0, 0, 205, 206, 1, 0, 0, 0, 206, 208, 1, 0, 0, 0, 207, 209, 3, 71, 35, 0, 208, 207, 1, 0, 0, 0, 209, 210, 1, 0, 0, 0, 210, 208, 1, 0, 0, 0, 210, 211, 1, 0, 0, 0, 211, 219, 1, 0, 0, 0, 212, 216, 5, 46, 0, 0, 213, 215, 3, 71, 35, 0, 214, 213, 1, 0, 0, 0, 215, 218, 1, 0, 0, 0, 216, 214, 1, 0, 0, 0, 216, 217, 1, 0, 0, 0, 217, 220, 1, 0, 0, 0, 218, 216, 1, 0, 0, 0, 219, 212, 1, 0, 0, 0, 219, 220, 1, 0, 0, 0, 220, 230, 1, 0, 0, 0, 221, 223, 7, 3, 0, 0, 222, 224, 3, 55, 27, 0, 223, 222, 1, 0, 0, 0, 223, 224, 1, 0, 0, 0, 224, 226, 1, 0, 0, 0, 225, 227, 3, 71, 35, 0, 226, 225, 1, 0, 0, 0, 227, 228, 1, 0, 0, 0, 228, 226, 1, 0, 0, 0, 228, 229, 1, 0, 0, 0, 229, 231, 1, 0, 0, 0, 230, 221, 1, 0, 0, 0, 230, 231, 1, 0, 0, 0, 231, 253, 1, 0, 0, 0, 232, 234, 3, 55, 27, 0, 233, 232, 1, 0, 0, 0, 233, 234, 1, 0, 0, 0, 234, 235, 1, 0, 0, 0, 235, 237, 5, 46, 0, 0, 236, 238, 3, 71, 35, 0, 237, 236, 1, 0, 0, 0, 238, 239, 1, 0, 0, 0, 239, 237, 1, 0, 0, 0, 239, 240, 1, 0, 0, 0, 240, 250, 1, 0, 0, 0, 241, 243, 7, 3, 0, 0, 242, 244, 3, 55, 27, 0, 243, 242, 1, 0, 0, 0, 243, 244, 1, 0, 0, 0, 244, 246, 1, 0, 0, 0, 245, 247, 3, 71, 35, 0, 246, 245, 1, 0, 0, 0, 247, 248, 1, 0, 0, 0, 248, 246, 1, 0, 0, 0, 248, 249, 1, 0, 0, 0, 249, 251, 1, 0, 0, 0, 250, 241, 1, 0, 0, 0, 250, 251, 1, 0, 0, 0, 251, 253, 1, 0, 0, 0, 252, 205, 1, 0, 0, 0, 252, 233, 1, 0, 0, 0, 253, 58, 1, 0, 0, 0, 254, 260, 5, 34, 0, 0, 255, 259, 8, 22, 0, 0, 256, 257, 5, 92, 0, 0, 257, 259, 9, 0, 0, 0, 258, 255, 1, 0, 0, 0, 258, 256, 1, 0, 0, 0, 259, 262, 1, 0, 0, 0, 260, 258, 1, 0, 0, 0, 260, 261, 1, 0, 0, 0, 261, 263, 1, 0, 0, 0, 262, 260, 1, 0, 0, 0, 263, 275, 5, 34, 0, 0, 264, 270, 5, 39, 0, 0, 265, 269, 8, 23, 0, 0, 266, 267, 5, 92, 0, 0, 267, 269, 9, 0, 0, 0, 268, 265, 1, 0, 0, 0, 268, 266, 1, 0, 0, 0, 269, 272, 1, 0, 0, 0, 270, 268, 1, 0, 0, 0, 270, 271, 1, 0, 0, 0, 271, 273, 1, 0, 0, 0, 272, 270, 1, 0, 0, 0, 273, 275, 5, 39, 0, 0, 274, 254, 1, 0, 0, 0, 274, 264, 1, 0, 0, 0, 275, 60, 1, 0, 0, 0, 276, 280, 7, 24, 0, 0, 277, 279, 7, 25, 0, 0, 278, 277, 1, 0, 0, 0, 279, 282, 1, 0, 0, 0, 280, 278, 1, 0, 0, 0, 280, 281, 1, 0, 0, 0, 281, 62, 1, 0, 0, 0, 282, 280, 1, 0, 0, 0, 283, 284, 5, 91, 0, 0, 284, 285, 5, 93, 0, 0, 285, 64, 1, 0, 0, 0, 286, 287, 5, 91, 0, 0, 287, 288, 5, 42, 0, 0, 288, 289, 5, 93, 0, 0, 289, 66, 1, 0, 0, 0, 290, 297, 3, 61, 30, 0, 291, 292, 5, 46, 0, 0, 292, 296, 3, 61, 30, 0, 293, 296, 3, 63, 31, 0, 294, 296, 3, 65, 32, 0, 295, 291, 1, 0, 0, 0, 295, 293, 1, 0, 0, 0, 295, 294, 1, 0, 0, 0, 296, 299, 1, 0, 0, 0, 297, 295, 1, 0, 0, 0, 297, 298, 1, 0, 0, 0, 298, 68, 1, 0, 0, 0, 299, 297, 1, 0, 0, 0, 300, 302, 7, 26, 0, 0, 301, 300, 1, 0, 0, 0, 302, 303, 1, 0, 0, 0, 303, 301, 1, 0, 0, 0, 303, 304, 1, 0, 0, 0, 304, 305, 1, 0, 0, 0, 305, 306, 6, 34, 0, 0, 306, 70, 1, 0, 0, 0, 307, 308, 7, 27, 0, 0, 308, 72, 1, 0, 0, 0, 309, 311, 8, 28, 0, 0, 310, 309, 1, 0, 0, 0, 311, 312, 1, 0, 0, 0, 312, 310, 1, 0, 0, 0, 312, 313, 1, 0, 0, 0, 313, 74, 1, 0, 0, 0, 28, 0, 88, 131, 148, 200, 205, 210, 216, 219, 223, 228, 230, 233, 239, 243, 248, 250, 252, 258, 260, 268, 270, 274, 280, 295, 297, 303, 312, 1, 6, 0, 0] \ No newline at end of file +[4, 0, 32, 320, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, 1, 0, 1, 0, 1, 1, 1, 1, 1, 2, 1, 2, 1, 3, 1, 3, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 3, 5, 89, 8, 5, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 3, 15, 132, 8, 15, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 3, 17, 149, 8, 17, 1, 18, 1, 18, 1, 18, 1, 19, 1, 19, 1, 19, 1, 19, 1, 20, 1, 20, 1, 20, 1, 20, 1, 21, 1, 21, 1, 21, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 23, 1, 23, 1, 23, 1, 23, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 3, 26, 201, 8, 26, 1, 27, 1, 27, 1, 28, 3, 28, 206, 8, 28, 1, 28, 4, 28, 209, 8, 28, 11, 28, 12, 28, 210, 1, 28, 1, 28, 5, 28, 215, 8, 28, 10, 28, 12, 28, 218, 9, 28, 3, 28, 220, 8, 28, 1, 28, 1, 28, 3, 28, 224, 8, 28, 1, 28, 4, 28, 227, 8, 28, 11, 28, 12, 28, 228, 3, 28, 231, 8, 28, 1, 28, 3, 28, 234, 8, 28, 1, 28, 1, 28, 4, 28, 238, 8, 28, 11, 28, 12, 28, 239, 1, 28, 1, 28, 3, 28, 244, 8, 28, 1, 28, 4, 28, 247, 8, 28, 11, 28, 12, 28, 248, 3, 28, 251, 8, 28, 3, 28, 253, 8, 28, 1, 29, 1, 29, 1, 29, 1, 29, 5, 29, 259, 8, 29, 10, 29, 12, 29, 262, 9, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 5, 29, 269, 8, 29, 10, 29, 12, 29, 272, 9, 29, 1, 29, 3, 29, 275, 8, 29, 1, 30, 1, 30, 5, 30, 279, 8, 30, 10, 30, 12, 30, 282, 9, 30, 1, 31, 1, 31, 1, 31, 1, 32, 1, 32, 1, 32, 1, 32, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 4, 33, 298, 8, 33, 11, 33, 12, 33, 299, 5, 33, 302, 8, 33, 10, 33, 12, 33, 305, 9, 33, 1, 34, 4, 34, 308, 8, 34, 11, 34, 12, 34, 309, 1, 34, 1, 34, 1, 35, 1, 35, 1, 36, 4, 36, 317, 8, 36, 11, 36, 12, 36, 318, 0, 0, 37, 1, 1, 3, 2, 5, 3, 7, 4, 9, 5, 11, 6, 13, 7, 15, 8, 17, 9, 19, 10, 21, 11, 23, 12, 25, 13, 27, 14, 29, 15, 31, 16, 33, 17, 35, 18, 37, 19, 39, 20, 41, 21, 43, 22, 45, 23, 47, 24, 49, 25, 51, 26, 53, 27, 55, 0, 57, 28, 59, 29, 61, 0, 63, 0, 65, 0, 67, 30, 69, 31, 71, 0, 73, 32, 1, 0, 29, 2, 0, 76, 76, 108, 108, 2, 0, 73, 73, 105, 105, 2, 0, 75, 75, 107, 107, 2, 0, 69, 69, 101, 101, 2, 0, 66, 66, 98, 98, 2, 0, 84, 84, 116, 116, 2, 0, 87, 87, 119, 119, 2, 0, 78, 78, 110, 110, 2, 0, 88, 88, 120, 120, 2, 0, 83, 83, 115, 115, 2, 0, 82, 82, 114, 114, 2, 0, 71, 71, 103, 103, 2, 0, 80, 80, 112, 112, 2, 0, 67, 67, 99, 99, 2, 0, 79, 79, 111, 111, 2, 0, 65, 65, 97, 97, 2, 0, 68, 68, 100, 100, 2, 0, 72, 72, 104, 104, 2, 0, 89, 89, 121, 121, 2, 0, 85, 85, 117, 117, 2, 0, 70, 70, 102, 102, 2, 0, 43, 43, 45, 45, 2, 0, 34, 34, 92, 92, 2, 0, 39, 39, 92, 92, 4, 0, 35, 36, 64, 90, 95, 95, 97, 123, 7, 0, 35, 36, 45, 45, 47, 58, 64, 90, 95, 95, 97, 123, 125, 125, 3, 0, 9, 10, 13, 13, 32, 32, 1, 0, 48, 57, 8, 0, 9, 10, 13, 13, 32, 34, 39, 41, 44, 44, 60, 62, 91, 91, 93, 93, 344, 0, 1, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 5, 1, 0, 0, 0, 0, 7, 1, 0, 0, 0, 0, 9, 1, 0, 0, 0, 0, 11, 1, 0, 0, 0, 0, 13, 1, 0, 0, 0, 0, 15, 1, 0, 0, 0, 0, 17, 1, 0, 0, 0, 0, 19, 1, 0, 0, 0, 0, 21, 1, 0, 0, 0, 0, 23, 1, 0, 0, 0, 0, 25, 1, 0, 0, 0, 0, 27, 1, 0, 0, 0, 0, 29, 1, 0, 0, 0, 0, 31, 1, 0, 0, 0, 0, 33, 1, 0, 0, 0, 0, 35, 1, 0, 0, 0, 0, 37, 1, 0, 0, 0, 0, 39, 1, 0, 0, 0, 0, 41, 1, 0, 0, 0, 0, 43, 1, 0, 0, 0, 0, 45, 1, 0, 0, 0, 0, 47, 1, 0, 0, 0, 0, 49, 1, 0, 0, 0, 0, 51, 1, 0, 0, 0, 0, 53, 1, 0, 0, 0, 0, 57, 1, 0, 0, 0, 0, 59, 1, 0, 0, 0, 0, 67, 1, 0, 0, 0, 0, 69, 1, 0, 0, 0, 0, 73, 1, 0, 0, 0, 1, 75, 1, 0, 0, 0, 3, 77, 1, 0, 0, 0, 5, 79, 1, 0, 0, 0, 7, 81, 1, 0, 0, 0, 9, 83, 1, 0, 0, 0, 11, 88, 1, 0, 0, 0, 13, 90, 1, 0, 0, 0, 15, 93, 1, 0, 0, 0, 17, 96, 1, 0, 0, 0, 19, 98, 1, 0, 0, 0, 21, 101, 1, 0, 0, 0, 23, 103, 1, 0, 0, 0, 25, 106, 1, 0, 0, 0, 27, 111, 1, 0, 0, 0, 29, 117, 1, 0, 0, 0, 31, 125, 1, 0, 0, 0, 33, 133, 1, 0, 0, 0, 35, 140, 1, 0, 0, 0, 37, 150, 1, 0, 0, 0, 39, 153, 1, 0, 0, 0, 41, 157, 1, 0, 0, 0, 43, 161, 1, 0, 0, 0, 45, 164, 1, 0, 0, 0, 47, 173, 1, 0, 0, 0, 49, 177, 1, 0, 0, 0, 51, 184, 1, 0, 0, 0, 53, 200, 1, 0, 0, 0, 55, 202, 1, 0, 0, 0, 57, 252, 1, 0, 0, 0, 59, 274, 1, 0, 0, 0, 61, 276, 1, 0, 0, 0, 63, 283, 1, 0, 0, 0, 65, 286, 1, 0, 0, 0, 67, 290, 1, 0, 0, 0, 69, 307, 1, 0, 0, 0, 71, 313, 1, 0, 0, 0, 73, 316, 1, 0, 0, 0, 75, 76, 5, 40, 0, 0, 76, 2, 1, 0, 0, 0, 77, 78, 5, 41, 0, 0, 78, 4, 1, 0, 0, 0, 79, 80, 5, 91, 0, 0, 80, 6, 1, 0, 0, 0, 81, 82, 5, 93, 0, 0, 82, 8, 1, 0, 0, 0, 83, 84, 5, 44, 0, 0, 84, 10, 1, 0, 0, 0, 85, 89, 5, 61, 0, 0, 86, 87, 5, 61, 0, 0, 87, 89, 5, 61, 0, 0, 88, 85, 1, 0, 0, 0, 88, 86, 1, 0, 0, 0, 89, 12, 1, 0, 0, 0, 90, 91, 5, 33, 0, 0, 91, 92, 5, 61, 0, 0, 92, 14, 1, 0, 0, 0, 93, 94, 5, 60, 0, 0, 94, 95, 5, 62, 0, 0, 95, 16, 1, 0, 0, 0, 96, 97, 5, 60, 0, 0, 97, 18, 1, 0, 0, 0, 98, 99, 5, 60, 0, 0, 99, 100, 5, 61, 0, 0, 100, 20, 1, 0, 0, 0, 101, 102, 5, 62, 0, 0, 102, 22, 1, 0, 0, 0, 103, 104, 5, 62, 0, 0, 104, 105, 5, 61, 0, 0, 105, 24, 1, 0, 0, 0, 106, 107, 7, 0, 0, 0, 107, 108, 7, 1, 0, 0, 108, 109, 7, 2, 0, 0, 109, 110, 7, 3, 0, 0, 110, 26, 1, 0, 0, 0, 111, 112, 7, 1, 0, 0, 112, 113, 7, 0, 0, 0, 113, 114, 7, 1, 0, 0, 114, 115, 7, 2, 0, 0, 115, 116, 7, 3, 0, 0, 116, 28, 1, 0, 0, 0, 117, 118, 7, 4, 0, 0, 118, 119, 7, 3, 0, 0, 119, 120, 7, 5, 0, 0, 120, 121, 7, 6, 0, 0, 121, 122, 7, 3, 0, 0, 122, 123, 7, 3, 0, 0, 123, 124, 7, 7, 0, 0, 124, 30, 1, 0, 0, 0, 125, 126, 7, 3, 0, 0, 126, 127, 7, 8, 0, 0, 127, 128, 7, 1, 0, 0, 128, 129, 7, 9, 0, 0, 129, 131, 7, 5, 0, 0, 130, 132, 7, 9, 0, 0, 131, 130, 1, 0, 0, 0, 131, 132, 1, 0, 0, 0, 132, 32, 1, 0, 0, 0, 133, 134, 7, 10, 0, 0, 134, 135, 7, 3, 0, 0, 135, 136, 7, 11, 0, 0, 136, 137, 7, 3, 0, 0, 137, 138, 7, 8, 0, 0, 138, 139, 7, 12, 0, 0, 139, 34, 1, 0, 0, 0, 140, 141, 7, 13, 0, 0, 141, 142, 7, 14, 0, 0, 142, 143, 7, 7, 0, 0, 143, 144, 7, 5, 0, 0, 144, 145, 7, 15, 0, 0, 145, 146, 7, 1, 0, 0, 146, 148, 7, 7, 0, 0, 147, 149, 7, 9, 0, 0, 148, 147, 1, 0, 0, 0, 148, 149, 1, 0, 0, 0, 149, 36, 1, 0, 0, 0, 150, 151, 7, 1, 0, 0, 151, 152, 7, 7, 0, 0, 152, 38, 1, 0, 0, 0, 153, 154, 7, 7, 0, 0, 154, 155, 7, 14, 0, 0, 155, 156, 7, 5, 0, 0, 156, 40, 1, 0, 0, 0, 157, 158, 7, 15, 0, 0, 158, 159, 7, 7, 0, 0, 159, 160, 7, 16, 0, 0, 160, 42, 1, 0, 0, 0, 161, 162, 7, 14, 0, 0, 162, 163, 7, 10, 0, 0, 163, 44, 1, 0, 0, 0, 164, 165, 7, 17, 0, 0, 165, 166, 7, 15, 0, 0, 166, 167, 7, 9, 0, 0, 167, 168, 7, 5, 0, 0, 168, 169, 7, 14, 0, 0, 169, 170, 7, 2, 0, 0, 170, 171, 7, 3, 0, 0, 171, 172, 7, 7, 0, 0, 172, 46, 1, 0, 0, 0, 173, 174, 7, 17, 0, 0, 174, 175, 7, 15, 0, 0, 175, 176, 7, 9, 0, 0, 176, 48, 1, 0, 0, 0, 177, 178, 7, 17, 0, 0, 178, 179, 7, 15, 0, 0, 179, 180, 7, 9, 0, 0, 180, 181, 7, 15, 0, 0, 181, 182, 7, 7, 0, 0, 182, 183, 7, 18, 0, 0, 183, 50, 1, 0, 0, 0, 184, 185, 7, 17, 0, 0, 185, 186, 7, 15, 0, 0, 186, 187, 7, 9, 0, 0, 187, 188, 7, 15, 0, 0, 188, 189, 7, 0, 0, 0, 189, 190, 7, 0, 0, 0, 190, 52, 1, 0, 0, 0, 191, 192, 7, 5, 0, 0, 192, 193, 7, 10, 0, 0, 193, 194, 7, 19, 0, 0, 194, 201, 7, 3, 0, 0, 195, 196, 7, 20, 0, 0, 196, 197, 7, 15, 0, 0, 197, 198, 7, 0, 0, 0, 198, 199, 7, 9, 0, 0, 199, 201, 7, 3, 0, 0, 200, 191, 1, 0, 0, 0, 200, 195, 1, 0, 0, 0, 201, 54, 1, 0, 0, 0, 202, 203, 7, 21, 0, 0, 203, 56, 1, 0, 0, 0, 204, 206, 3, 55, 27, 0, 205, 204, 1, 0, 0, 0, 205, 206, 1, 0, 0, 0, 206, 208, 1, 0, 0, 0, 207, 209, 3, 71, 35, 0, 208, 207, 1, 0, 0, 0, 209, 210, 1, 0, 0, 0, 210, 208, 1, 0, 0, 0, 210, 211, 1, 0, 0, 0, 211, 219, 1, 0, 0, 0, 212, 216, 5, 46, 0, 0, 213, 215, 3, 71, 35, 0, 214, 213, 1, 0, 0, 0, 215, 218, 1, 0, 0, 0, 216, 214, 1, 0, 0, 0, 216, 217, 1, 0, 0, 0, 217, 220, 1, 0, 0, 0, 218, 216, 1, 0, 0, 0, 219, 212, 1, 0, 0, 0, 219, 220, 1, 0, 0, 0, 220, 230, 1, 0, 0, 0, 221, 223, 7, 3, 0, 0, 222, 224, 3, 55, 27, 0, 223, 222, 1, 0, 0, 0, 223, 224, 1, 0, 0, 0, 224, 226, 1, 0, 0, 0, 225, 227, 3, 71, 35, 0, 226, 225, 1, 0, 0, 0, 227, 228, 1, 0, 0, 0, 228, 226, 1, 0, 0, 0, 228, 229, 1, 0, 0, 0, 229, 231, 1, 0, 0, 0, 230, 221, 1, 0, 0, 0, 230, 231, 1, 0, 0, 0, 231, 253, 1, 0, 0, 0, 232, 234, 3, 55, 27, 0, 233, 232, 1, 0, 0, 0, 233, 234, 1, 0, 0, 0, 234, 235, 1, 0, 0, 0, 235, 237, 5, 46, 0, 0, 236, 238, 3, 71, 35, 0, 237, 236, 1, 0, 0, 0, 238, 239, 1, 0, 0, 0, 239, 237, 1, 0, 0, 0, 239, 240, 1, 0, 0, 0, 240, 250, 1, 0, 0, 0, 241, 243, 7, 3, 0, 0, 242, 244, 3, 55, 27, 0, 243, 242, 1, 0, 0, 0, 243, 244, 1, 0, 0, 0, 244, 246, 1, 0, 0, 0, 245, 247, 3, 71, 35, 0, 246, 245, 1, 0, 0, 0, 247, 248, 1, 0, 0, 0, 248, 246, 1, 0, 0, 0, 248, 249, 1, 0, 0, 0, 249, 251, 1, 0, 0, 0, 250, 241, 1, 0, 0, 0, 250, 251, 1, 0, 0, 0, 251, 253, 1, 0, 0, 0, 252, 205, 1, 0, 0, 0, 252, 233, 1, 0, 0, 0, 253, 58, 1, 0, 0, 0, 254, 260, 5, 34, 0, 0, 255, 259, 8, 22, 0, 0, 256, 257, 5, 92, 0, 0, 257, 259, 9, 0, 0, 0, 258, 255, 1, 0, 0, 0, 258, 256, 1, 0, 0, 0, 259, 262, 1, 0, 0, 0, 260, 258, 1, 0, 0, 0, 260, 261, 1, 0, 0, 0, 261, 263, 1, 0, 0, 0, 262, 260, 1, 0, 0, 0, 263, 275, 5, 34, 0, 0, 264, 270, 5, 39, 0, 0, 265, 269, 8, 23, 0, 0, 266, 267, 5, 92, 0, 0, 267, 269, 9, 0, 0, 0, 268, 265, 1, 0, 0, 0, 268, 266, 1, 0, 0, 0, 269, 272, 1, 0, 0, 0, 270, 268, 1, 0, 0, 0, 270, 271, 1, 0, 0, 0, 271, 273, 1, 0, 0, 0, 272, 270, 1, 0, 0, 0, 273, 275, 5, 39, 0, 0, 274, 254, 1, 0, 0, 0, 274, 264, 1, 0, 0, 0, 275, 60, 1, 0, 0, 0, 276, 280, 7, 24, 0, 0, 277, 279, 7, 25, 0, 0, 278, 277, 1, 0, 0, 0, 279, 282, 1, 0, 0, 0, 280, 278, 1, 0, 0, 0, 280, 281, 1, 0, 0, 0, 281, 62, 1, 0, 0, 0, 282, 280, 1, 0, 0, 0, 283, 284, 5, 91, 0, 0, 284, 285, 5, 93, 0, 0, 285, 64, 1, 0, 0, 0, 286, 287, 5, 91, 0, 0, 287, 288, 5, 42, 0, 0, 288, 289, 5, 93, 0, 0, 289, 66, 1, 0, 0, 0, 290, 303, 3, 61, 30, 0, 291, 292, 5, 46, 0, 0, 292, 302, 3, 61, 30, 0, 293, 302, 3, 63, 31, 0, 294, 302, 3, 65, 32, 0, 295, 297, 5, 46, 0, 0, 296, 298, 3, 71, 35, 0, 297, 296, 1, 0, 0, 0, 298, 299, 1, 0, 0, 0, 299, 297, 1, 0, 0, 0, 299, 300, 1, 0, 0, 0, 300, 302, 1, 0, 0, 0, 301, 291, 1, 0, 0, 0, 301, 293, 1, 0, 0, 0, 301, 294, 1, 0, 0, 0, 301, 295, 1, 0, 0, 0, 302, 305, 1, 0, 0, 0, 303, 301, 1, 0, 0, 0, 303, 304, 1, 0, 0, 0, 304, 68, 1, 0, 0, 0, 305, 303, 1, 0, 0, 0, 306, 308, 7, 26, 0, 0, 307, 306, 1, 0, 0, 0, 308, 309, 1, 0, 0, 0, 309, 307, 1, 0, 0, 0, 309, 310, 1, 0, 0, 0, 310, 311, 1, 0, 0, 0, 311, 312, 6, 34, 0, 0, 312, 70, 1, 0, 0, 0, 313, 314, 7, 27, 0, 0, 314, 72, 1, 0, 0, 0, 315, 317, 8, 28, 0, 0, 316, 315, 1, 0, 0, 0, 317, 318, 1, 0, 0, 0, 318, 316, 1, 0, 0, 0, 318, 319, 1, 0, 0, 0, 319, 74, 1, 0, 0, 0, 29, 0, 88, 131, 148, 200, 205, 210, 216, 219, 223, 228, 230, 233, 239, 243, 248, 250, 252, 258, 260, 268, 270, 274, 280, 299, 301, 303, 309, 318, 1, 6, 0, 0] \ No newline at end of file diff --git a/frontend/src/parser/FilterQueryLexer.ts b/frontend/src/parser/FilterQueryLexer.ts index fe4d66029230..26f468a81306 100644 --- a/frontend/src/parser/FilterQueryLexer.ts +++ b/frontend/src/parser/FilterQueryLexer.ts @@ -1,4 +1,4 @@ -// Generated from ../../../../grammar/FilterQuery.g4 by ANTLR 4.13.1 +// Generated from FilterQuery.g4 by ANTLR 4.13.1 // noinspection ES6UnusedImports,JSUnusedGlobalSymbols,JSUnusedLocalSymbols import { ATN, @@ -100,7 +100,7 @@ export default class FilterQueryLexer extends Lexer { public get modeNames(): string[] { return FilterQueryLexer.modeNames; } - public static readonly _serializedATN: number[] = [4,0,32,314,6,-1,2,0, + public static readonly _serializedATN: number[] = [4,0,32,320,6,-1,2,0, 7,0,2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5,2,6,7,6,2,7,7,7,2,8,7,8,2,9, 7,9,2,10,7,10,2,11,7,11,2,12,7,12,2,13,7,13,2,14,7,14,2,15,7,15,2,16,7, 16,2,17,7,17,2,18,7,18,2,19,7,19,2,20,7,20,2,21,7,21,2,22,7,22,2,23,7,23, @@ -122,94 +122,97 @@ export default class FilterQueryLexer extends Lexer { 3,28,253,8,28,1,29,1,29,1,29,1,29,5,29,259,8,29,10,29,12,29,262,9,29,1, 29,1,29,1,29,1,29,1,29,5,29,269,8,29,10,29,12,29,272,9,29,1,29,3,29,275, 8,29,1,30,1,30,5,30,279,8,30,10,30,12,30,282,9,30,1,31,1,31,1,31,1,32,1, - 32,1,32,1,32,1,33,1,33,1,33,1,33,1,33,5,33,296,8,33,10,33,12,33,299,9,33, - 1,34,4,34,302,8,34,11,34,12,34,303,1,34,1,34,1,35,1,35,1,36,4,36,311,8, - 36,11,36,12,36,312,0,0,37,1,1,3,2,5,3,7,4,9,5,11,6,13,7,15,8,17,9,19,10, - 21,11,23,12,25,13,27,14,29,15,31,16,33,17,35,18,37,19,39,20,41,21,43,22, - 45,23,47,24,49,25,51,26,53,27,55,0,57,28,59,29,61,0,63,0,65,0,67,30,69, - 31,71,0,73,32,1,0,29,2,0,76,76,108,108,2,0,73,73,105,105,2,0,75,75,107, - 107,2,0,69,69,101,101,2,0,66,66,98,98,2,0,84,84,116,116,2,0,87,87,119,119, - 2,0,78,78,110,110,2,0,88,88,120,120,2,0,83,83,115,115,2,0,82,82,114,114, - 2,0,71,71,103,103,2,0,80,80,112,112,2,0,67,67,99,99,2,0,79,79,111,111,2, - 0,65,65,97,97,2,0,68,68,100,100,2,0,72,72,104,104,2,0,89,89,121,121,2,0, - 85,85,117,117,2,0,70,70,102,102,2,0,43,43,45,45,2,0,34,34,92,92,2,0,39, - 39,92,92,4,0,36,36,65,90,95,95,97,122,6,0,36,36,45,45,47,58,65,90,95,95, - 97,122,3,0,9,10,13,13,32,32,1,0,48,57,8,0,9,10,13,13,32,34,39,41,44,44, - 60,62,91,91,93,93,336,0,1,1,0,0,0,0,3,1,0,0,0,0,5,1,0,0,0,0,7,1,0,0,0,0, - 9,1,0,0,0,0,11,1,0,0,0,0,13,1,0,0,0,0,15,1,0,0,0,0,17,1,0,0,0,0,19,1,0, - 0,0,0,21,1,0,0,0,0,23,1,0,0,0,0,25,1,0,0,0,0,27,1,0,0,0,0,29,1,0,0,0,0, - 31,1,0,0,0,0,33,1,0,0,0,0,35,1,0,0,0,0,37,1,0,0,0,0,39,1,0,0,0,0,41,1,0, - 0,0,0,43,1,0,0,0,0,45,1,0,0,0,0,47,1,0,0,0,0,49,1,0,0,0,0,51,1,0,0,0,0, - 53,1,0,0,0,0,57,1,0,0,0,0,59,1,0,0,0,0,67,1,0,0,0,0,69,1,0,0,0,0,73,1,0, - 0,0,1,75,1,0,0,0,3,77,1,0,0,0,5,79,1,0,0,0,7,81,1,0,0,0,9,83,1,0,0,0,11, - 88,1,0,0,0,13,90,1,0,0,0,15,93,1,0,0,0,17,96,1,0,0,0,19,98,1,0,0,0,21,101, - 1,0,0,0,23,103,1,0,0,0,25,106,1,0,0,0,27,111,1,0,0,0,29,117,1,0,0,0,31, - 125,1,0,0,0,33,133,1,0,0,0,35,140,1,0,0,0,37,150,1,0,0,0,39,153,1,0,0,0, - 41,157,1,0,0,0,43,161,1,0,0,0,45,164,1,0,0,0,47,173,1,0,0,0,49,177,1,0, - 0,0,51,184,1,0,0,0,53,200,1,0,0,0,55,202,1,0,0,0,57,252,1,0,0,0,59,274, - 1,0,0,0,61,276,1,0,0,0,63,283,1,0,0,0,65,286,1,0,0,0,67,290,1,0,0,0,69, - 301,1,0,0,0,71,307,1,0,0,0,73,310,1,0,0,0,75,76,5,40,0,0,76,2,1,0,0,0,77, - 78,5,41,0,0,78,4,1,0,0,0,79,80,5,91,0,0,80,6,1,0,0,0,81,82,5,93,0,0,82, - 8,1,0,0,0,83,84,5,44,0,0,84,10,1,0,0,0,85,89,5,61,0,0,86,87,5,61,0,0,87, - 89,5,61,0,0,88,85,1,0,0,0,88,86,1,0,0,0,89,12,1,0,0,0,90,91,5,33,0,0,91, - 92,5,61,0,0,92,14,1,0,0,0,93,94,5,60,0,0,94,95,5,62,0,0,95,16,1,0,0,0,96, - 97,5,60,0,0,97,18,1,0,0,0,98,99,5,60,0,0,99,100,5,61,0,0,100,20,1,0,0,0, - 101,102,5,62,0,0,102,22,1,0,0,0,103,104,5,62,0,0,104,105,5,61,0,0,105,24, - 1,0,0,0,106,107,7,0,0,0,107,108,7,1,0,0,108,109,7,2,0,0,109,110,7,3,0,0, - 110,26,1,0,0,0,111,112,7,1,0,0,112,113,7,0,0,0,113,114,7,1,0,0,114,115, - 7,2,0,0,115,116,7,3,0,0,116,28,1,0,0,0,117,118,7,4,0,0,118,119,7,3,0,0, - 119,120,7,5,0,0,120,121,7,6,0,0,121,122,7,3,0,0,122,123,7,3,0,0,123,124, - 7,7,0,0,124,30,1,0,0,0,125,126,7,3,0,0,126,127,7,8,0,0,127,128,7,1,0,0, - 128,129,7,9,0,0,129,131,7,5,0,0,130,132,7,9,0,0,131,130,1,0,0,0,131,132, - 1,0,0,0,132,32,1,0,0,0,133,134,7,10,0,0,134,135,7,3,0,0,135,136,7,11,0, - 0,136,137,7,3,0,0,137,138,7,8,0,0,138,139,7,12,0,0,139,34,1,0,0,0,140,141, - 7,13,0,0,141,142,7,14,0,0,142,143,7,7,0,0,143,144,7,5,0,0,144,145,7,15, - 0,0,145,146,7,1,0,0,146,148,7,7,0,0,147,149,7,9,0,0,148,147,1,0,0,0,148, - 149,1,0,0,0,149,36,1,0,0,0,150,151,7,1,0,0,151,152,7,7,0,0,152,38,1,0,0, - 0,153,154,7,7,0,0,154,155,7,14,0,0,155,156,7,5,0,0,156,40,1,0,0,0,157,158, - 7,15,0,0,158,159,7,7,0,0,159,160,7,16,0,0,160,42,1,0,0,0,161,162,7,14,0, - 0,162,163,7,10,0,0,163,44,1,0,0,0,164,165,7,17,0,0,165,166,7,15,0,0,166, - 167,7,9,0,0,167,168,7,5,0,0,168,169,7,14,0,0,169,170,7,2,0,0,170,171,7, - 3,0,0,171,172,7,7,0,0,172,46,1,0,0,0,173,174,7,17,0,0,174,175,7,15,0,0, - 175,176,7,9,0,0,176,48,1,0,0,0,177,178,7,17,0,0,178,179,7,15,0,0,179,180, - 7,9,0,0,180,181,7,15,0,0,181,182,7,7,0,0,182,183,7,18,0,0,183,50,1,0,0, - 0,184,185,7,17,0,0,185,186,7,15,0,0,186,187,7,9,0,0,187,188,7,15,0,0,188, - 189,7,0,0,0,189,190,7,0,0,0,190,52,1,0,0,0,191,192,7,5,0,0,192,193,7,10, - 0,0,193,194,7,19,0,0,194,201,7,3,0,0,195,196,7,20,0,0,196,197,7,15,0,0, - 197,198,7,0,0,0,198,199,7,9,0,0,199,201,7,3,0,0,200,191,1,0,0,0,200,195, - 1,0,0,0,201,54,1,0,0,0,202,203,7,21,0,0,203,56,1,0,0,0,204,206,3,55,27, - 0,205,204,1,0,0,0,205,206,1,0,0,0,206,208,1,0,0,0,207,209,3,71,35,0,208, - 207,1,0,0,0,209,210,1,0,0,0,210,208,1,0,0,0,210,211,1,0,0,0,211,219,1,0, - 0,0,212,216,5,46,0,0,213,215,3,71,35,0,214,213,1,0,0,0,215,218,1,0,0,0, - 216,214,1,0,0,0,216,217,1,0,0,0,217,220,1,0,0,0,218,216,1,0,0,0,219,212, - 1,0,0,0,219,220,1,0,0,0,220,230,1,0,0,0,221,223,7,3,0,0,222,224,3,55,27, - 0,223,222,1,0,0,0,223,224,1,0,0,0,224,226,1,0,0,0,225,227,3,71,35,0,226, - 225,1,0,0,0,227,228,1,0,0,0,228,226,1,0,0,0,228,229,1,0,0,0,229,231,1,0, - 0,0,230,221,1,0,0,0,230,231,1,0,0,0,231,253,1,0,0,0,232,234,3,55,27,0,233, - 232,1,0,0,0,233,234,1,0,0,0,234,235,1,0,0,0,235,237,5,46,0,0,236,238,3, - 71,35,0,237,236,1,0,0,0,238,239,1,0,0,0,239,237,1,0,0,0,239,240,1,0,0,0, - 240,250,1,0,0,0,241,243,7,3,0,0,242,244,3,55,27,0,243,242,1,0,0,0,243,244, - 1,0,0,0,244,246,1,0,0,0,245,247,3,71,35,0,246,245,1,0,0,0,247,248,1,0,0, - 0,248,246,1,0,0,0,248,249,1,0,0,0,249,251,1,0,0,0,250,241,1,0,0,0,250,251, - 1,0,0,0,251,253,1,0,0,0,252,205,1,0,0,0,252,233,1,0,0,0,253,58,1,0,0,0, - 254,260,5,34,0,0,255,259,8,22,0,0,256,257,5,92,0,0,257,259,9,0,0,0,258, - 255,1,0,0,0,258,256,1,0,0,0,259,262,1,0,0,0,260,258,1,0,0,0,260,261,1,0, - 0,0,261,263,1,0,0,0,262,260,1,0,0,0,263,275,5,34,0,0,264,270,5,39,0,0,265, - 269,8,23,0,0,266,267,5,92,0,0,267,269,9,0,0,0,268,265,1,0,0,0,268,266,1, - 0,0,0,269,272,1,0,0,0,270,268,1,0,0,0,270,271,1,0,0,0,271,273,1,0,0,0,272, - 270,1,0,0,0,273,275,5,39,0,0,274,254,1,0,0,0,274,264,1,0,0,0,275,60,1,0, - 0,0,276,280,7,24,0,0,277,279,7,25,0,0,278,277,1,0,0,0,279,282,1,0,0,0,280, - 278,1,0,0,0,280,281,1,0,0,0,281,62,1,0,0,0,282,280,1,0,0,0,283,284,5,91, - 0,0,284,285,5,93,0,0,285,64,1,0,0,0,286,287,5,91,0,0,287,288,5,42,0,0,288, - 289,5,93,0,0,289,66,1,0,0,0,290,297,3,61,30,0,291,292,5,46,0,0,292,296, - 3,61,30,0,293,296,3,63,31,0,294,296,3,65,32,0,295,291,1,0,0,0,295,293,1, - 0,0,0,295,294,1,0,0,0,296,299,1,0,0,0,297,295,1,0,0,0,297,298,1,0,0,0,298, - 68,1,0,0,0,299,297,1,0,0,0,300,302,7,26,0,0,301,300,1,0,0,0,302,303,1,0, - 0,0,303,301,1,0,0,0,303,304,1,0,0,0,304,305,1,0,0,0,305,306,6,34,0,0,306, - 70,1,0,0,0,307,308,7,27,0,0,308,72,1,0,0,0,309,311,8,28,0,0,310,309,1,0, - 0,0,311,312,1,0,0,0,312,310,1,0,0,0,312,313,1,0,0,0,313,74,1,0,0,0,28,0, - 88,131,148,200,205,210,216,219,223,228,230,233,239,243,248,250,252,258, - 260,268,270,274,280,295,297,303,312,1,6,0,0]; + 32,1,32,1,32,1,33,1,33,1,33,1,33,1,33,1,33,1,33,4,33,298,8,33,11,33,12, + 33,299,5,33,302,8,33,10,33,12,33,305,9,33,1,34,4,34,308,8,34,11,34,12,34, + 309,1,34,1,34,1,35,1,35,1,36,4,36,317,8,36,11,36,12,36,318,0,0,37,1,1,3, + 2,5,3,7,4,9,5,11,6,13,7,15,8,17,9,19,10,21,11,23,12,25,13,27,14,29,15,31, + 16,33,17,35,18,37,19,39,20,41,21,43,22,45,23,47,24,49,25,51,26,53,27,55, + 0,57,28,59,29,61,0,63,0,65,0,67,30,69,31,71,0,73,32,1,0,29,2,0,76,76,108, + 108,2,0,73,73,105,105,2,0,75,75,107,107,2,0,69,69,101,101,2,0,66,66,98, + 98,2,0,84,84,116,116,2,0,87,87,119,119,2,0,78,78,110,110,2,0,88,88,120, + 120,2,0,83,83,115,115,2,0,82,82,114,114,2,0,71,71,103,103,2,0,80,80,112, + 112,2,0,67,67,99,99,2,0,79,79,111,111,2,0,65,65,97,97,2,0,68,68,100,100, + 2,0,72,72,104,104,2,0,89,89,121,121,2,0,85,85,117,117,2,0,70,70,102,102, + 2,0,43,43,45,45,2,0,34,34,92,92,2,0,39,39,92,92,4,0,35,36,64,90,95,95,97, + 123,7,0,35,36,45,45,47,58,64,90,95,95,97,123,125,125,3,0,9,10,13,13,32, + 32,1,0,48,57,8,0,9,10,13,13,32,34,39,41,44,44,60,62,91,91,93,93,344,0,1, + 1,0,0,0,0,3,1,0,0,0,0,5,1,0,0,0,0,7,1,0,0,0,0,9,1,0,0,0,0,11,1,0,0,0,0, + 13,1,0,0,0,0,15,1,0,0,0,0,17,1,0,0,0,0,19,1,0,0,0,0,21,1,0,0,0,0,23,1,0, + 0,0,0,25,1,0,0,0,0,27,1,0,0,0,0,29,1,0,0,0,0,31,1,0,0,0,0,33,1,0,0,0,0, + 35,1,0,0,0,0,37,1,0,0,0,0,39,1,0,0,0,0,41,1,0,0,0,0,43,1,0,0,0,0,45,1,0, + 0,0,0,47,1,0,0,0,0,49,1,0,0,0,0,51,1,0,0,0,0,53,1,0,0,0,0,57,1,0,0,0,0, + 59,1,0,0,0,0,67,1,0,0,0,0,69,1,0,0,0,0,73,1,0,0,0,1,75,1,0,0,0,3,77,1,0, + 0,0,5,79,1,0,0,0,7,81,1,0,0,0,9,83,1,0,0,0,11,88,1,0,0,0,13,90,1,0,0,0, + 15,93,1,0,0,0,17,96,1,0,0,0,19,98,1,0,0,0,21,101,1,0,0,0,23,103,1,0,0,0, + 25,106,1,0,0,0,27,111,1,0,0,0,29,117,1,0,0,0,31,125,1,0,0,0,33,133,1,0, + 0,0,35,140,1,0,0,0,37,150,1,0,0,0,39,153,1,0,0,0,41,157,1,0,0,0,43,161, + 1,0,0,0,45,164,1,0,0,0,47,173,1,0,0,0,49,177,1,0,0,0,51,184,1,0,0,0,53, + 200,1,0,0,0,55,202,1,0,0,0,57,252,1,0,0,0,59,274,1,0,0,0,61,276,1,0,0,0, + 63,283,1,0,0,0,65,286,1,0,0,0,67,290,1,0,0,0,69,307,1,0,0,0,71,313,1,0, + 0,0,73,316,1,0,0,0,75,76,5,40,0,0,76,2,1,0,0,0,77,78,5,41,0,0,78,4,1,0, + 0,0,79,80,5,91,0,0,80,6,1,0,0,0,81,82,5,93,0,0,82,8,1,0,0,0,83,84,5,44, + 0,0,84,10,1,0,0,0,85,89,5,61,0,0,86,87,5,61,0,0,87,89,5,61,0,0,88,85,1, + 0,0,0,88,86,1,0,0,0,89,12,1,0,0,0,90,91,5,33,0,0,91,92,5,61,0,0,92,14,1, + 0,0,0,93,94,5,60,0,0,94,95,5,62,0,0,95,16,1,0,0,0,96,97,5,60,0,0,97,18, + 1,0,0,0,98,99,5,60,0,0,99,100,5,61,0,0,100,20,1,0,0,0,101,102,5,62,0,0, + 102,22,1,0,0,0,103,104,5,62,0,0,104,105,5,61,0,0,105,24,1,0,0,0,106,107, + 7,0,0,0,107,108,7,1,0,0,108,109,7,2,0,0,109,110,7,3,0,0,110,26,1,0,0,0, + 111,112,7,1,0,0,112,113,7,0,0,0,113,114,7,1,0,0,114,115,7,2,0,0,115,116, + 7,3,0,0,116,28,1,0,0,0,117,118,7,4,0,0,118,119,7,3,0,0,119,120,7,5,0,0, + 120,121,7,6,0,0,121,122,7,3,0,0,122,123,7,3,0,0,123,124,7,7,0,0,124,30, + 1,0,0,0,125,126,7,3,0,0,126,127,7,8,0,0,127,128,7,1,0,0,128,129,7,9,0,0, + 129,131,7,5,0,0,130,132,7,9,0,0,131,130,1,0,0,0,131,132,1,0,0,0,132,32, + 1,0,0,0,133,134,7,10,0,0,134,135,7,3,0,0,135,136,7,11,0,0,136,137,7,3,0, + 0,137,138,7,8,0,0,138,139,7,12,0,0,139,34,1,0,0,0,140,141,7,13,0,0,141, + 142,7,14,0,0,142,143,7,7,0,0,143,144,7,5,0,0,144,145,7,15,0,0,145,146,7, + 1,0,0,146,148,7,7,0,0,147,149,7,9,0,0,148,147,1,0,0,0,148,149,1,0,0,0,149, + 36,1,0,0,0,150,151,7,1,0,0,151,152,7,7,0,0,152,38,1,0,0,0,153,154,7,7,0, + 0,154,155,7,14,0,0,155,156,7,5,0,0,156,40,1,0,0,0,157,158,7,15,0,0,158, + 159,7,7,0,0,159,160,7,16,0,0,160,42,1,0,0,0,161,162,7,14,0,0,162,163,7, + 10,0,0,163,44,1,0,0,0,164,165,7,17,0,0,165,166,7,15,0,0,166,167,7,9,0,0, + 167,168,7,5,0,0,168,169,7,14,0,0,169,170,7,2,0,0,170,171,7,3,0,0,171,172, + 7,7,0,0,172,46,1,0,0,0,173,174,7,17,0,0,174,175,7,15,0,0,175,176,7,9,0, + 0,176,48,1,0,0,0,177,178,7,17,0,0,178,179,7,15,0,0,179,180,7,9,0,0,180, + 181,7,15,0,0,181,182,7,7,0,0,182,183,7,18,0,0,183,50,1,0,0,0,184,185,7, + 17,0,0,185,186,7,15,0,0,186,187,7,9,0,0,187,188,7,15,0,0,188,189,7,0,0, + 0,189,190,7,0,0,0,190,52,1,0,0,0,191,192,7,5,0,0,192,193,7,10,0,0,193,194, + 7,19,0,0,194,201,7,3,0,0,195,196,7,20,0,0,196,197,7,15,0,0,197,198,7,0, + 0,0,198,199,7,9,0,0,199,201,7,3,0,0,200,191,1,0,0,0,200,195,1,0,0,0,201, + 54,1,0,0,0,202,203,7,21,0,0,203,56,1,0,0,0,204,206,3,55,27,0,205,204,1, + 0,0,0,205,206,1,0,0,0,206,208,1,0,0,0,207,209,3,71,35,0,208,207,1,0,0,0, + 209,210,1,0,0,0,210,208,1,0,0,0,210,211,1,0,0,0,211,219,1,0,0,0,212,216, + 5,46,0,0,213,215,3,71,35,0,214,213,1,0,0,0,215,218,1,0,0,0,216,214,1,0, + 0,0,216,217,1,0,0,0,217,220,1,0,0,0,218,216,1,0,0,0,219,212,1,0,0,0,219, + 220,1,0,0,0,220,230,1,0,0,0,221,223,7,3,0,0,222,224,3,55,27,0,223,222,1, + 0,0,0,223,224,1,0,0,0,224,226,1,0,0,0,225,227,3,71,35,0,226,225,1,0,0,0, + 227,228,1,0,0,0,228,226,1,0,0,0,228,229,1,0,0,0,229,231,1,0,0,0,230,221, + 1,0,0,0,230,231,1,0,0,0,231,253,1,0,0,0,232,234,3,55,27,0,233,232,1,0,0, + 0,233,234,1,0,0,0,234,235,1,0,0,0,235,237,5,46,0,0,236,238,3,71,35,0,237, + 236,1,0,0,0,238,239,1,0,0,0,239,237,1,0,0,0,239,240,1,0,0,0,240,250,1,0, + 0,0,241,243,7,3,0,0,242,244,3,55,27,0,243,242,1,0,0,0,243,244,1,0,0,0,244, + 246,1,0,0,0,245,247,3,71,35,0,246,245,1,0,0,0,247,248,1,0,0,0,248,246,1, + 0,0,0,248,249,1,0,0,0,249,251,1,0,0,0,250,241,1,0,0,0,250,251,1,0,0,0,251, + 253,1,0,0,0,252,205,1,0,0,0,252,233,1,0,0,0,253,58,1,0,0,0,254,260,5,34, + 0,0,255,259,8,22,0,0,256,257,5,92,0,0,257,259,9,0,0,0,258,255,1,0,0,0,258, + 256,1,0,0,0,259,262,1,0,0,0,260,258,1,0,0,0,260,261,1,0,0,0,261,263,1,0, + 0,0,262,260,1,0,0,0,263,275,5,34,0,0,264,270,5,39,0,0,265,269,8,23,0,0, + 266,267,5,92,0,0,267,269,9,0,0,0,268,265,1,0,0,0,268,266,1,0,0,0,269,272, + 1,0,0,0,270,268,1,0,0,0,270,271,1,0,0,0,271,273,1,0,0,0,272,270,1,0,0,0, + 273,275,5,39,0,0,274,254,1,0,0,0,274,264,1,0,0,0,275,60,1,0,0,0,276,280, + 7,24,0,0,277,279,7,25,0,0,278,277,1,0,0,0,279,282,1,0,0,0,280,278,1,0,0, + 0,280,281,1,0,0,0,281,62,1,0,0,0,282,280,1,0,0,0,283,284,5,91,0,0,284,285, + 5,93,0,0,285,64,1,0,0,0,286,287,5,91,0,0,287,288,5,42,0,0,288,289,5,93, + 0,0,289,66,1,0,0,0,290,303,3,61,30,0,291,292,5,46,0,0,292,302,3,61,30,0, + 293,302,3,63,31,0,294,302,3,65,32,0,295,297,5,46,0,0,296,298,3,71,35,0, + 297,296,1,0,0,0,298,299,1,0,0,0,299,297,1,0,0,0,299,300,1,0,0,0,300,302, + 1,0,0,0,301,291,1,0,0,0,301,293,1,0,0,0,301,294,1,0,0,0,301,295,1,0,0,0, + 302,305,1,0,0,0,303,301,1,0,0,0,303,304,1,0,0,0,304,68,1,0,0,0,305,303, + 1,0,0,0,306,308,7,26,0,0,307,306,1,0,0,0,308,309,1,0,0,0,309,307,1,0,0, + 0,309,310,1,0,0,0,310,311,1,0,0,0,311,312,6,34,0,0,312,70,1,0,0,0,313,314, + 7,27,0,0,314,72,1,0,0,0,315,317,8,28,0,0,316,315,1,0,0,0,317,318,1,0,0, + 0,318,316,1,0,0,0,318,319,1,0,0,0,319,74,1,0,0,0,29,0,88,131,148,200,205, + 210,216,219,223,228,230,233,239,243,248,250,252,258,260,268,270,274,280, + 299,301,303,309,318,1,6,0,0]; private static __ATN: ATN; public static get _ATN(): ATN { diff --git a/frontend/src/parser/FilterQueryListener.ts b/frontend/src/parser/FilterQueryListener.ts index a05a158de3a9..8fd9bf9c28f8 100644 --- a/frontend/src/parser/FilterQueryListener.ts +++ b/frontend/src/parser/FilterQueryListener.ts @@ -1,4 +1,4 @@ -// Generated from ../../../../grammar/FilterQuery.g4 by ANTLR 4.13.1 +// Generated from FilterQuery.g4 by ANTLR 4.13.1 import {ParseTreeListener} from "antlr4"; diff --git a/frontend/src/parser/FilterQueryParser.ts b/frontend/src/parser/FilterQueryParser.ts index d9c11a1646f9..bb66f2ef58fc 100644 --- a/frontend/src/parser/FilterQueryParser.ts +++ b/frontend/src/parser/FilterQueryParser.ts @@ -1,4 +1,4 @@ -// Generated from ../../../../grammar/FilterQuery.g4 by ANTLR 4.13.1 +// Generated from FilterQuery.g4 by ANTLR 4.13.1 // noinspection ES6UnusedImports,JSUnusedGlobalSymbols,JSUnusedLocalSymbols import { diff --git a/frontend/src/parser/FilterQueryVisitor.ts b/frontend/src/parser/FilterQueryVisitor.ts index 9ffa568cf36e..50578ecb7a7f 100644 --- a/frontend/src/parser/FilterQueryVisitor.ts +++ b/frontend/src/parser/FilterQueryVisitor.ts @@ -1,4 +1,4 @@ -// Generated from ../../../../grammar/FilterQuery.g4 by ANTLR 4.13.1 +// Generated from FilterQuery.g4 by ANTLR 4.13.1 import {ParseTreeVisitor} from 'antlr4'; diff --git a/grammar/FilterQuery.g4 b/grammar/FilterQuery.g4 index 18174ab01e41..405090510eba 100644 --- a/grammar/FilterQuery.g4 +++ b/grammar/FilterQuery.g4 @@ -207,12 +207,12 @@ QUOTED_TEXT ) ; -fragment SEGMENT : [a-zA-Z$_] [a-zA-Z0-9$_:\-/]* ; +fragment SEGMENT : [a-zA-Z$_@{#] [a-zA-Z0-9$_@#{}:\-/]* ; fragment EMPTY_BRACKS : '[' ']' ; fragment OLD_JSON_BRACKS: '[' '*' ']'; KEY - : SEGMENT ( '.' SEGMENT | EMPTY_BRACKS | OLD_JSON_BRACKS)* + : SEGMENT ( '.' SEGMENT | EMPTY_BRACKS | OLD_JSON_BRACKS | '.' DIGIT+)* ; // Ignore whitespace diff --git a/pkg/parser/grammar/FilterQueryLexer.interp b/pkg/parser/grammar/FilterQueryLexer.interp index 6f3d2d7e1697..bb681c909134 100644 --- a/pkg/parser/grammar/FilterQueryLexer.interp +++ b/pkg/parser/grammar/FilterQueryLexer.interp @@ -115,4 +115,4 @@ mode names: DEFAULT_MODE atn: -[4, 0, 32, 314, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, 1, 0, 1, 0, 1, 1, 1, 1, 1, 2, 1, 2, 1, 3, 1, 3, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 3, 5, 89, 8, 5, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 3, 15, 132, 8, 15, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 3, 17, 149, 8, 17, 1, 18, 1, 18, 1, 18, 1, 19, 1, 19, 1, 19, 1, 19, 1, 20, 1, 20, 1, 20, 1, 20, 1, 21, 1, 21, 1, 21, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 23, 1, 23, 1, 23, 1, 23, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 3, 26, 201, 8, 26, 1, 27, 1, 27, 1, 28, 3, 28, 206, 8, 28, 1, 28, 4, 28, 209, 8, 28, 11, 28, 12, 28, 210, 1, 28, 1, 28, 5, 28, 215, 8, 28, 10, 28, 12, 28, 218, 9, 28, 3, 28, 220, 8, 28, 1, 28, 1, 28, 3, 28, 224, 8, 28, 1, 28, 4, 28, 227, 8, 28, 11, 28, 12, 28, 228, 3, 28, 231, 8, 28, 1, 28, 3, 28, 234, 8, 28, 1, 28, 1, 28, 4, 28, 238, 8, 28, 11, 28, 12, 28, 239, 1, 28, 1, 28, 3, 28, 244, 8, 28, 1, 28, 4, 28, 247, 8, 28, 11, 28, 12, 28, 248, 3, 28, 251, 8, 28, 3, 28, 253, 8, 28, 1, 29, 1, 29, 1, 29, 1, 29, 5, 29, 259, 8, 29, 10, 29, 12, 29, 262, 9, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 5, 29, 269, 8, 29, 10, 29, 12, 29, 272, 9, 29, 1, 29, 3, 29, 275, 8, 29, 1, 30, 1, 30, 5, 30, 279, 8, 30, 10, 30, 12, 30, 282, 9, 30, 1, 31, 1, 31, 1, 31, 1, 32, 1, 32, 1, 32, 1, 32, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 5, 33, 296, 8, 33, 10, 33, 12, 33, 299, 9, 33, 1, 34, 4, 34, 302, 8, 34, 11, 34, 12, 34, 303, 1, 34, 1, 34, 1, 35, 1, 35, 1, 36, 4, 36, 311, 8, 36, 11, 36, 12, 36, 312, 0, 0, 37, 1, 1, 3, 2, 5, 3, 7, 4, 9, 5, 11, 6, 13, 7, 15, 8, 17, 9, 19, 10, 21, 11, 23, 12, 25, 13, 27, 14, 29, 15, 31, 16, 33, 17, 35, 18, 37, 19, 39, 20, 41, 21, 43, 22, 45, 23, 47, 24, 49, 25, 51, 26, 53, 27, 55, 0, 57, 28, 59, 29, 61, 0, 63, 0, 65, 0, 67, 30, 69, 31, 71, 0, 73, 32, 1, 0, 29, 2, 0, 76, 76, 108, 108, 2, 0, 73, 73, 105, 105, 2, 0, 75, 75, 107, 107, 2, 0, 69, 69, 101, 101, 2, 0, 66, 66, 98, 98, 2, 0, 84, 84, 116, 116, 2, 0, 87, 87, 119, 119, 2, 0, 78, 78, 110, 110, 2, 0, 88, 88, 120, 120, 2, 0, 83, 83, 115, 115, 2, 0, 82, 82, 114, 114, 2, 0, 71, 71, 103, 103, 2, 0, 80, 80, 112, 112, 2, 0, 67, 67, 99, 99, 2, 0, 79, 79, 111, 111, 2, 0, 65, 65, 97, 97, 2, 0, 68, 68, 100, 100, 2, 0, 72, 72, 104, 104, 2, 0, 89, 89, 121, 121, 2, 0, 85, 85, 117, 117, 2, 0, 70, 70, 102, 102, 2, 0, 43, 43, 45, 45, 2, 0, 34, 34, 92, 92, 2, 0, 39, 39, 92, 92, 4, 0, 36, 36, 65, 90, 95, 95, 97, 122, 6, 0, 36, 36, 45, 45, 47, 58, 65, 90, 95, 95, 97, 122, 3, 0, 9, 10, 13, 13, 32, 32, 1, 0, 48, 57, 8, 0, 9, 10, 13, 13, 32, 34, 39, 41, 44, 44, 60, 62, 91, 91, 93, 93, 336, 0, 1, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 5, 1, 0, 0, 0, 0, 7, 1, 0, 0, 0, 0, 9, 1, 0, 0, 0, 0, 11, 1, 0, 0, 0, 0, 13, 1, 0, 0, 0, 0, 15, 1, 0, 0, 0, 0, 17, 1, 0, 0, 0, 0, 19, 1, 0, 0, 0, 0, 21, 1, 0, 0, 0, 0, 23, 1, 0, 0, 0, 0, 25, 1, 0, 0, 0, 0, 27, 1, 0, 0, 0, 0, 29, 1, 0, 0, 0, 0, 31, 1, 0, 0, 0, 0, 33, 1, 0, 0, 0, 0, 35, 1, 0, 0, 0, 0, 37, 1, 0, 0, 0, 0, 39, 1, 0, 0, 0, 0, 41, 1, 0, 0, 0, 0, 43, 1, 0, 0, 0, 0, 45, 1, 0, 0, 0, 0, 47, 1, 0, 0, 0, 0, 49, 1, 0, 0, 0, 0, 51, 1, 0, 0, 0, 0, 53, 1, 0, 0, 0, 0, 57, 1, 0, 0, 0, 0, 59, 1, 0, 0, 0, 0, 67, 1, 0, 0, 0, 0, 69, 1, 0, 0, 0, 0, 73, 1, 0, 0, 0, 1, 75, 1, 0, 0, 0, 3, 77, 1, 0, 0, 0, 5, 79, 1, 0, 0, 0, 7, 81, 1, 0, 0, 0, 9, 83, 1, 0, 0, 0, 11, 88, 1, 0, 0, 0, 13, 90, 1, 0, 0, 0, 15, 93, 1, 0, 0, 0, 17, 96, 1, 0, 0, 0, 19, 98, 1, 0, 0, 0, 21, 101, 1, 0, 0, 0, 23, 103, 1, 0, 0, 0, 25, 106, 1, 0, 0, 0, 27, 111, 1, 0, 0, 0, 29, 117, 1, 0, 0, 0, 31, 125, 1, 0, 0, 0, 33, 133, 1, 0, 0, 0, 35, 140, 1, 0, 0, 0, 37, 150, 1, 0, 0, 0, 39, 153, 1, 0, 0, 0, 41, 157, 1, 0, 0, 0, 43, 161, 1, 0, 0, 0, 45, 164, 1, 0, 0, 0, 47, 173, 1, 0, 0, 0, 49, 177, 1, 0, 0, 0, 51, 184, 1, 0, 0, 0, 53, 200, 1, 0, 0, 0, 55, 202, 1, 0, 0, 0, 57, 252, 1, 0, 0, 0, 59, 274, 1, 0, 0, 0, 61, 276, 1, 0, 0, 0, 63, 283, 1, 0, 0, 0, 65, 286, 1, 0, 0, 0, 67, 290, 1, 0, 0, 0, 69, 301, 1, 0, 0, 0, 71, 307, 1, 0, 0, 0, 73, 310, 1, 0, 0, 0, 75, 76, 5, 40, 0, 0, 76, 2, 1, 0, 0, 0, 77, 78, 5, 41, 0, 0, 78, 4, 1, 0, 0, 0, 79, 80, 5, 91, 0, 0, 80, 6, 1, 0, 0, 0, 81, 82, 5, 93, 0, 0, 82, 8, 1, 0, 0, 0, 83, 84, 5, 44, 0, 0, 84, 10, 1, 0, 0, 0, 85, 89, 5, 61, 0, 0, 86, 87, 5, 61, 0, 0, 87, 89, 5, 61, 0, 0, 88, 85, 1, 0, 0, 0, 88, 86, 1, 0, 0, 0, 89, 12, 1, 0, 0, 0, 90, 91, 5, 33, 0, 0, 91, 92, 5, 61, 0, 0, 92, 14, 1, 0, 0, 0, 93, 94, 5, 60, 0, 0, 94, 95, 5, 62, 0, 0, 95, 16, 1, 0, 0, 0, 96, 97, 5, 60, 0, 0, 97, 18, 1, 0, 0, 0, 98, 99, 5, 60, 0, 0, 99, 100, 5, 61, 0, 0, 100, 20, 1, 0, 0, 0, 101, 102, 5, 62, 0, 0, 102, 22, 1, 0, 0, 0, 103, 104, 5, 62, 0, 0, 104, 105, 5, 61, 0, 0, 105, 24, 1, 0, 0, 0, 106, 107, 7, 0, 0, 0, 107, 108, 7, 1, 0, 0, 108, 109, 7, 2, 0, 0, 109, 110, 7, 3, 0, 0, 110, 26, 1, 0, 0, 0, 111, 112, 7, 1, 0, 0, 112, 113, 7, 0, 0, 0, 113, 114, 7, 1, 0, 0, 114, 115, 7, 2, 0, 0, 115, 116, 7, 3, 0, 0, 116, 28, 1, 0, 0, 0, 117, 118, 7, 4, 0, 0, 118, 119, 7, 3, 0, 0, 119, 120, 7, 5, 0, 0, 120, 121, 7, 6, 0, 0, 121, 122, 7, 3, 0, 0, 122, 123, 7, 3, 0, 0, 123, 124, 7, 7, 0, 0, 124, 30, 1, 0, 0, 0, 125, 126, 7, 3, 0, 0, 126, 127, 7, 8, 0, 0, 127, 128, 7, 1, 0, 0, 128, 129, 7, 9, 0, 0, 129, 131, 7, 5, 0, 0, 130, 132, 7, 9, 0, 0, 131, 130, 1, 0, 0, 0, 131, 132, 1, 0, 0, 0, 132, 32, 1, 0, 0, 0, 133, 134, 7, 10, 0, 0, 134, 135, 7, 3, 0, 0, 135, 136, 7, 11, 0, 0, 136, 137, 7, 3, 0, 0, 137, 138, 7, 8, 0, 0, 138, 139, 7, 12, 0, 0, 139, 34, 1, 0, 0, 0, 140, 141, 7, 13, 0, 0, 141, 142, 7, 14, 0, 0, 142, 143, 7, 7, 0, 0, 143, 144, 7, 5, 0, 0, 144, 145, 7, 15, 0, 0, 145, 146, 7, 1, 0, 0, 146, 148, 7, 7, 0, 0, 147, 149, 7, 9, 0, 0, 148, 147, 1, 0, 0, 0, 148, 149, 1, 0, 0, 0, 149, 36, 1, 0, 0, 0, 150, 151, 7, 1, 0, 0, 151, 152, 7, 7, 0, 0, 152, 38, 1, 0, 0, 0, 153, 154, 7, 7, 0, 0, 154, 155, 7, 14, 0, 0, 155, 156, 7, 5, 0, 0, 156, 40, 1, 0, 0, 0, 157, 158, 7, 15, 0, 0, 158, 159, 7, 7, 0, 0, 159, 160, 7, 16, 0, 0, 160, 42, 1, 0, 0, 0, 161, 162, 7, 14, 0, 0, 162, 163, 7, 10, 0, 0, 163, 44, 1, 0, 0, 0, 164, 165, 7, 17, 0, 0, 165, 166, 7, 15, 0, 0, 166, 167, 7, 9, 0, 0, 167, 168, 7, 5, 0, 0, 168, 169, 7, 14, 0, 0, 169, 170, 7, 2, 0, 0, 170, 171, 7, 3, 0, 0, 171, 172, 7, 7, 0, 0, 172, 46, 1, 0, 0, 0, 173, 174, 7, 17, 0, 0, 174, 175, 7, 15, 0, 0, 175, 176, 7, 9, 0, 0, 176, 48, 1, 0, 0, 0, 177, 178, 7, 17, 0, 0, 178, 179, 7, 15, 0, 0, 179, 180, 7, 9, 0, 0, 180, 181, 7, 15, 0, 0, 181, 182, 7, 7, 0, 0, 182, 183, 7, 18, 0, 0, 183, 50, 1, 0, 0, 0, 184, 185, 7, 17, 0, 0, 185, 186, 7, 15, 0, 0, 186, 187, 7, 9, 0, 0, 187, 188, 7, 15, 0, 0, 188, 189, 7, 0, 0, 0, 189, 190, 7, 0, 0, 0, 190, 52, 1, 0, 0, 0, 191, 192, 7, 5, 0, 0, 192, 193, 7, 10, 0, 0, 193, 194, 7, 19, 0, 0, 194, 201, 7, 3, 0, 0, 195, 196, 7, 20, 0, 0, 196, 197, 7, 15, 0, 0, 197, 198, 7, 0, 0, 0, 198, 199, 7, 9, 0, 0, 199, 201, 7, 3, 0, 0, 200, 191, 1, 0, 0, 0, 200, 195, 1, 0, 0, 0, 201, 54, 1, 0, 0, 0, 202, 203, 7, 21, 0, 0, 203, 56, 1, 0, 0, 0, 204, 206, 3, 55, 27, 0, 205, 204, 1, 0, 0, 0, 205, 206, 1, 0, 0, 0, 206, 208, 1, 0, 0, 0, 207, 209, 3, 71, 35, 0, 208, 207, 1, 0, 0, 0, 209, 210, 1, 0, 0, 0, 210, 208, 1, 0, 0, 0, 210, 211, 1, 0, 0, 0, 211, 219, 1, 0, 0, 0, 212, 216, 5, 46, 0, 0, 213, 215, 3, 71, 35, 0, 214, 213, 1, 0, 0, 0, 215, 218, 1, 0, 0, 0, 216, 214, 1, 0, 0, 0, 216, 217, 1, 0, 0, 0, 217, 220, 1, 0, 0, 0, 218, 216, 1, 0, 0, 0, 219, 212, 1, 0, 0, 0, 219, 220, 1, 0, 0, 0, 220, 230, 1, 0, 0, 0, 221, 223, 7, 3, 0, 0, 222, 224, 3, 55, 27, 0, 223, 222, 1, 0, 0, 0, 223, 224, 1, 0, 0, 0, 224, 226, 1, 0, 0, 0, 225, 227, 3, 71, 35, 0, 226, 225, 1, 0, 0, 0, 227, 228, 1, 0, 0, 0, 228, 226, 1, 0, 0, 0, 228, 229, 1, 0, 0, 0, 229, 231, 1, 0, 0, 0, 230, 221, 1, 0, 0, 0, 230, 231, 1, 0, 0, 0, 231, 253, 1, 0, 0, 0, 232, 234, 3, 55, 27, 0, 233, 232, 1, 0, 0, 0, 233, 234, 1, 0, 0, 0, 234, 235, 1, 0, 0, 0, 235, 237, 5, 46, 0, 0, 236, 238, 3, 71, 35, 0, 237, 236, 1, 0, 0, 0, 238, 239, 1, 0, 0, 0, 239, 237, 1, 0, 0, 0, 239, 240, 1, 0, 0, 0, 240, 250, 1, 0, 0, 0, 241, 243, 7, 3, 0, 0, 242, 244, 3, 55, 27, 0, 243, 242, 1, 0, 0, 0, 243, 244, 1, 0, 0, 0, 244, 246, 1, 0, 0, 0, 245, 247, 3, 71, 35, 0, 246, 245, 1, 0, 0, 0, 247, 248, 1, 0, 0, 0, 248, 246, 1, 0, 0, 0, 248, 249, 1, 0, 0, 0, 249, 251, 1, 0, 0, 0, 250, 241, 1, 0, 0, 0, 250, 251, 1, 0, 0, 0, 251, 253, 1, 0, 0, 0, 252, 205, 1, 0, 0, 0, 252, 233, 1, 0, 0, 0, 253, 58, 1, 0, 0, 0, 254, 260, 5, 34, 0, 0, 255, 259, 8, 22, 0, 0, 256, 257, 5, 92, 0, 0, 257, 259, 9, 0, 0, 0, 258, 255, 1, 0, 0, 0, 258, 256, 1, 0, 0, 0, 259, 262, 1, 0, 0, 0, 260, 258, 1, 0, 0, 0, 260, 261, 1, 0, 0, 0, 261, 263, 1, 0, 0, 0, 262, 260, 1, 0, 0, 0, 263, 275, 5, 34, 0, 0, 264, 270, 5, 39, 0, 0, 265, 269, 8, 23, 0, 0, 266, 267, 5, 92, 0, 0, 267, 269, 9, 0, 0, 0, 268, 265, 1, 0, 0, 0, 268, 266, 1, 0, 0, 0, 269, 272, 1, 0, 0, 0, 270, 268, 1, 0, 0, 0, 270, 271, 1, 0, 0, 0, 271, 273, 1, 0, 0, 0, 272, 270, 1, 0, 0, 0, 273, 275, 5, 39, 0, 0, 274, 254, 1, 0, 0, 0, 274, 264, 1, 0, 0, 0, 275, 60, 1, 0, 0, 0, 276, 280, 7, 24, 0, 0, 277, 279, 7, 25, 0, 0, 278, 277, 1, 0, 0, 0, 279, 282, 1, 0, 0, 0, 280, 278, 1, 0, 0, 0, 280, 281, 1, 0, 0, 0, 281, 62, 1, 0, 0, 0, 282, 280, 1, 0, 0, 0, 283, 284, 5, 91, 0, 0, 284, 285, 5, 93, 0, 0, 285, 64, 1, 0, 0, 0, 286, 287, 5, 91, 0, 0, 287, 288, 5, 42, 0, 0, 288, 289, 5, 93, 0, 0, 289, 66, 1, 0, 0, 0, 290, 297, 3, 61, 30, 0, 291, 292, 5, 46, 0, 0, 292, 296, 3, 61, 30, 0, 293, 296, 3, 63, 31, 0, 294, 296, 3, 65, 32, 0, 295, 291, 1, 0, 0, 0, 295, 293, 1, 0, 0, 0, 295, 294, 1, 0, 0, 0, 296, 299, 1, 0, 0, 0, 297, 295, 1, 0, 0, 0, 297, 298, 1, 0, 0, 0, 298, 68, 1, 0, 0, 0, 299, 297, 1, 0, 0, 0, 300, 302, 7, 26, 0, 0, 301, 300, 1, 0, 0, 0, 302, 303, 1, 0, 0, 0, 303, 301, 1, 0, 0, 0, 303, 304, 1, 0, 0, 0, 304, 305, 1, 0, 0, 0, 305, 306, 6, 34, 0, 0, 306, 70, 1, 0, 0, 0, 307, 308, 7, 27, 0, 0, 308, 72, 1, 0, 0, 0, 309, 311, 8, 28, 0, 0, 310, 309, 1, 0, 0, 0, 311, 312, 1, 0, 0, 0, 312, 310, 1, 0, 0, 0, 312, 313, 1, 0, 0, 0, 313, 74, 1, 0, 0, 0, 28, 0, 88, 131, 148, 200, 205, 210, 216, 219, 223, 228, 230, 233, 239, 243, 248, 250, 252, 258, 260, 268, 270, 274, 280, 295, 297, 303, 312, 1, 6, 0, 0] \ No newline at end of file +[4, 0, 32, 320, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, 1, 0, 1, 0, 1, 1, 1, 1, 1, 2, 1, 2, 1, 3, 1, 3, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 3, 5, 89, 8, 5, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 3, 15, 132, 8, 15, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 3, 17, 149, 8, 17, 1, 18, 1, 18, 1, 18, 1, 19, 1, 19, 1, 19, 1, 19, 1, 20, 1, 20, 1, 20, 1, 20, 1, 21, 1, 21, 1, 21, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 23, 1, 23, 1, 23, 1, 23, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 3, 26, 201, 8, 26, 1, 27, 1, 27, 1, 28, 3, 28, 206, 8, 28, 1, 28, 4, 28, 209, 8, 28, 11, 28, 12, 28, 210, 1, 28, 1, 28, 5, 28, 215, 8, 28, 10, 28, 12, 28, 218, 9, 28, 3, 28, 220, 8, 28, 1, 28, 1, 28, 3, 28, 224, 8, 28, 1, 28, 4, 28, 227, 8, 28, 11, 28, 12, 28, 228, 3, 28, 231, 8, 28, 1, 28, 3, 28, 234, 8, 28, 1, 28, 1, 28, 4, 28, 238, 8, 28, 11, 28, 12, 28, 239, 1, 28, 1, 28, 3, 28, 244, 8, 28, 1, 28, 4, 28, 247, 8, 28, 11, 28, 12, 28, 248, 3, 28, 251, 8, 28, 3, 28, 253, 8, 28, 1, 29, 1, 29, 1, 29, 1, 29, 5, 29, 259, 8, 29, 10, 29, 12, 29, 262, 9, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 5, 29, 269, 8, 29, 10, 29, 12, 29, 272, 9, 29, 1, 29, 3, 29, 275, 8, 29, 1, 30, 1, 30, 5, 30, 279, 8, 30, 10, 30, 12, 30, 282, 9, 30, 1, 31, 1, 31, 1, 31, 1, 32, 1, 32, 1, 32, 1, 32, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 4, 33, 298, 8, 33, 11, 33, 12, 33, 299, 5, 33, 302, 8, 33, 10, 33, 12, 33, 305, 9, 33, 1, 34, 4, 34, 308, 8, 34, 11, 34, 12, 34, 309, 1, 34, 1, 34, 1, 35, 1, 35, 1, 36, 4, 36, 317, 8, 36, 11, 36, 12, 36, 318, 0, 0, 37, 1, 1, 3, 2, 5, 3, 7, 4, 9, 5, 11, 6, 13, 7, 15, 8, 17, 9, 19, 10, 21, 11, 23, 12, 25, 13, 27, 14, 29, 15, 31, 16, 33, 17, 35, 18, 37, 19, 39, 20, 41, 21, 43, 22, 45, 23, 47, 24, 49, 25, 51, 26, 53, 27, 55, 0, 57, 28, 59, 29, 61, 0, 63, 0, 65, 0, 67, 30, 69, 31, 71, 0, 73, 32, 1, 0, 29, 2, 0, 76, 76, 108, 108, 2, 0, 73, 73, 105, 105, 2, 0, 75, 75, 107, 107, 2, 0, 69, 69, 101, 101, 2, 0, 66, 66, 98, 98, 2, 0, 84, 84, 116, 116, 2, 0, 87, 87, 119, 119, 2, 0, 78, 78, 110, 110, 2, 0, 88, 88, 120, 120, 2, 0, 83, 83, 115, 115, 2, 0, 82, 82, 114, 114, 2, 0, 71, 71, 103, 103, 2, 0, 80, 80, 112, 112, 2, 0, 67, 67, 99, 99, 2, 0, 79, 79, 111, 111, 2, 0, 65, 65, 97, 97, 2, 0, 68, 68, 100, 100, 2, 0, 72, 72, 104, 104, 2, 0, 89, 89, 121, 121, 2, 0, 85, 85, 117, 117, 2, 0, 70, 70, 102, 102, 2, 0, 43, 43, 45, 45, 2, 0, 34, 34, 92, 92, 2, 0, 39, 39, 92, 92, 4, 0, 35, 36, 64, 90, 95, 95, 97, 123, 7, 0, 35, 36, 45, 45, 47, 58, 64, 90, 95, 95, 97, 123, 125, 125, 3, 0, 9, 10, 13, 13, 32, 32, 1, 0, 48, 57, 8, 0, 9, 10, 13, 13, 32, 34, 39, 41, 44, 44, 60, 62, 91, 91, 93, 93, 344, 0, 1, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 5, 1, 0, 0, 0, 0, 7, 1, 0, 0, 0, 0, 9, 1, 0, 0, 0, 0, 11, 1, 0, 0, 0, 0, 13, 1, 0, 0, 0, 0, 15, 1, 0, 0, 0, 0, 17, 1, 0, 0, 0, 0, 19, 1, 0, 0, 0, 0, 21, 1, 0, 0, 0, 0, 23, 1, 0, 0, 0, 0, 25, 1, 0, 0, 0, 0, 27, 1, 0, 0, 0, 0, 29, 1, 0, 0, 0, 0, 31, 1, 0, 0, 0, 0, 33, 1, 0, 0, 0, 0, 35, 1, 0, 0, 0, 0, 37, 1, 0, 0, 0, 0, 39, 1, 0, 0, 0, 0, 41, 1, 0, 0, 0, 0, 43, 1, 0, 0, 0, 0, 45, 1, 0, 0, 0, 0, 47, 1, 0, 0, 0, 0, 49, 1, 0, 0, 0, 0, 51, 1, 0, 0, 0, 0, 53, 1, 0, 0, 0, 0, 57, 1, 0, 0, 0, 0, 59, 1, 0, 0, 0, 0, 67, 1, 0, 0, 0, 0, 69, 1, 0, 0, 0, 0, 73, 1, 0, 0, 0, 1, 75, 1, 0, 0, 0, 3, 77, 1, 0, 0, 0, 5, 79, 1, 0, 0, 0, 7, 81, 1, 0, 0, 0, 9, 83, 1, 0, 0, 0, 11, 88, 1, 0, 0, 0, 13, 90, 1, 0, 0, 0, 15, 93, 1, 0, 0, 0, 17, 96, 1, 0, 0, 0, 19, 98, 1, 0, 0, 0, 21, 101, 1, 0, 0, 0, 23, 103, 1, 0, 0, 0, 25, 106, 1, 0, 0, 0, 27, 111, 1, 0, 0, 0, 29, 117, 1, 0, 0, 0, 31, 125, 1, 0, 0, 0, 33, 133, 1, 0, 0, 0, 35, 140, 1, 0, 0, 0, 37, 150, 1, 0, 0, 0, 39, 153, 1, 0, 0, 0, 41, 157, 1, 0, 0, 0, 43, 161, 1, 0, 0, 0, 45, 164, 1, 0, 0, 0, 47, 173, 1, 0, 0, 0, 49, 177, 1, 0, 0, 0, 51, 184, 1, 0, 0, 0, 53, 200, 1, 0, 0, 0, 55, 202, 1, 0, 0, 0, 57, 252, 1, 0, 0, 0, 59, 274, 1, 0, 0, 0, 61, 276, 1, 0, 0, 0, 63, 283, 1, 0, 0, 0, 65, 286, 1, 0, 0, 0, 67, 290, 1, 0, 0, 0, 69, 307, 1, 0, 0, 0, 71, 313, 1, 0, 0, 0, 73, 316, 1, 0, 0, 0, 75, 76, 5, 40, 0, 0, 76, 2, 1, 0, 0, 0, 77, 78, 5, 41, 0, 0, 78, 4, 1, 0, 0, 0, 79, 80, 5, 91, 0, 0, 80, 6, 1, 0, 0, 0, 81, 82, 5, 93, 0, 0, 82, 8, 1, 0, 0, 0, 83, 84, 5, 44, 0, 0, 84, 10, 1, 0, 0, 0, 85, 89, 5, 61, 0, 0, 86, 87, 5, 61, 0, 0, 87, 89, 5, 61, 0, 0, 88, 85, 1, 0, 0, 0, 88, 86, 1, 0, 0, 0, 89, 12, 1, 0, 0, 0, 90, 91, 5, 33, 0, 0, 91, 92, 5, 61, 0, 0, 92, 14, 1, 0, 0, 0, 93, 94, 5, 60, 0, 0, 94, 95, 5, 62, 0, 0, 95, 16, 1, 0, 0, 0, 96, 97, 5, 60, 0, 0, 97, 18, 1, 0, 0, 0, 98, 99, 5, 60, 0, 0, 99, 100, 5, 61, 0, 0, 100, 20, 1, 0, 0, 0, 101, 102, 5, 62, 0, 0, 102, 22, 1, 0, 0, 0, 103, 104, 5, 62, 0, 0, 104, 105, 5, 61, 0, 0, 105, 24, 1, 0, 0, 0, 106, 107, 7, 0, 0, 0, 107, 108, 7, 1, 0, 0, 108, 109, 7, 2, 0, 0, 109, 110, 7, 3, 0, 0, 110, 26, 1, 0, 0, 0, 111, 112, 7, 1, 0, 0, 112, 113, 7, 0, 0, 0, 113, 114, 7, 1, 0, 0, 114, 115, 7, 2, 0, 0, 115, 116, 7, 3, 0, 0, 116, 28, 1, 0, 0, 0, 117, 118, 7, 4, 0, 0, 118, 119, 7, 3, 0, 0, 119, 120, 7, 5, 0, 0, 120, 121, 7, 6, 0, 0, 121, 122, 7, 3, 0, 0, 122, 123, 7, 3, 0, 0, 123, 124, 7, 7, 0, 0, 124, 30, 1, 0, 0, 0, 125, 126, 7, 3, 0, 0, 126, 127, 7, 8, 0, 0, 127, 128, 7, 1, 0, 0, 128, 129, 7, 9, 0, 0, 129, 131, 7, 5, 0, 0, 130, 132, 7, 9, 0, 0, 131, 130, 1, 0, 0, 0, 131, 132, 1, 0, 0, 0, 132, 32, 1, 0, 0, 0, 133, 134, 7, 10, 0, 0, 134, 135, 7, 3, 0, 0, 135, 136, 7, 11, 0, 0, 136, 137, 7, 3, 0, 0, 137, 138, 7, 8, 0, 0, 138, 139, 7, 12, 0, 0, 139, 34, 1, 0, 0, 0, 140, 141, 7, 13, 0, 0, 141, 142, 7, 14, 0, 0, 142, 143, 7, 7, 0, 0, 143, 144, 7, 5, 0, 0, 144, 145, 7, 15, 0, 0, 145, 146, 7, 1, 0, 0, 146, 148, 7, 7, 0, 0, 147, 149, 7, 9, 0, 0, 148, 147, 1, 0, 0, 0, 148, 149, 1, 0, 0, 0, 149, 36, 1, 0, 0, 0, 150, 151, 7, 1, 0, 0, 151, 152, 7, 7, 0, 0, 152, 38, 1, 0, 0, 0, 153, 154, 7, 7, 0, 0, 154, 155, 7, 14, 0, 0, 155, 156, 7, 5, 0, 0, 156, 40, 1, 0, 0, 0, 157, 158, 7, 15, 0, 0, 158, 159, 7, 7, 0, 0, 159, 160, 7, 16, 0, 0, 160, 42, 1, 0, 0, 0, 161, 162, 7, 14, 0, 0, 162, 163, 7, 10, 0, 0, 163, 44, 1, 0, 0, 0, 164, 165, 7, 17, 0, 0, 165, 166, 7, 15, 0, 0, 166, 167, 7, 9, 0, 0, 167, 168, 7, 5, 0, 0, 168, 169, 7, 14, 0, 0, 169, 170, 7, 2, 0, 0, 170, 171, 7, 3, 0, 0, 171, 172, 7, 7, 0, 0, 172, 46, 1, 0, 0, 0, 173, 174, 7, 17, 0, 0, 174, 175, 7, 15, 0, 0, 175, 176, 7, 9, 0, 0, 176, 48, 1, 0, 0, 0, 177, 178, 7, 17, 0, 0, 178, 179, 7, 15, 0, 0, 179, 180, 7, 9, 0, 0, 180, 181, 7, 15, 0, 0, 181, 182, 7, 7, 0, 0, 182, 183, 7, 18, 0, 0, 183, 50, 1, 0, 0, 0, 184, 185, 7, 17, 0, 0, 185, 186, 7, 15, 0, 0, 186, 187, 7, 9, 0, 0, 187, 188, 7, 15, 0, 0, 188, 189, 7, 0, 0, 0, 189, 190, 7, 0, 0, 0, 190, 52, 1, 0, 0, 0, 191, 192, 7, 5, 0, 0, 192, 193, 7, 10, 0, 0, 193, 194, 7, 19, 0, 0, 194, 201, 7, 3, 0, 0, 195, 196, 7, 20, 0, 0, 196, 197, 7, 15, 0, 0, 197, 198, 7, 0, 0, 0, 198, 199, 7, 9, 0, 0, 199, 201, 7, 3, 0, 0, 200, 191, 1, 0, 0, 0, 200, 195, 1, 0, 0, 0, 201, 54, 1, 0, 0, 0, 202, 203, 7, 21, 0, 0, 203, 56, 1, 0, 0, 0, 204, 206, 3, 55, 27, 0, 205, 204, 1, 0, 0, 0, 205, 206, 1, 0, 0, 0, 206, 208, 1, 0, 0, 0, 207, 209, 3, 71, 35, 0, 208, 207, 1, 0, 0, 0, 209, 210, 1, 0, 0, 0, 210, 208, 1, 0, 0, 0, 210, 211, 1, 0, 0, 0, 211, 219, 1, 0, 0, 0, 212, 216, 5, 46, 0, 0, 213, 215, 3, 71, 35, 0, 214, 213, 1, 0, 0, 0, 215, 218, 1, 0, 0, 0, 216, 214, 1, 0, 0, 0, 216, 217, 1, 0, 0, 0, 217, 220, 1, 0, 0, 0, 218, 216, 1, 0, 0, 0, 219, 212, 1, 0, 0, 0, 219, 220, 1, 0, 0, 0, 220, 230, 1, 0, 0, 0, 221, 223, 7, 3, 0, 0, 222, 224, 3, 55, 27, 0, 223, 222, 1, 0, 0, 0, 223, 224, 1, 0, 0, 0, 224, 226, 1, 0, 0, 0, 225, 227, 3, 71, 35, 0, 226, 225, 1, 0, 0, 0, 227, 228, 1, 0, 0, 0, 228, 226, 1, 0, 0, 0, 228, 229, 1, 0, 0, 0, 229, 231, 1, 0, 0, 0, 230, 221, 1, 0, 0, 0, 230, 231, 1, 0, 0, 0, 231, 253, 1, 0, 0, 0, 232, 234, 3, 55, 27, 0, 233, 232, 1, 0, 0, 0, 233, 234, 1, 0, 0, 0, 234, 235, 1, 0, 0, 0, 235, 237, 5, 46, 0, 0, 236, 238, 3, 71, 35, 0, 237, 236, 1, 0, 0, 0, 238, 239, 1, 0, 0, 0, 239, 237, 1, 0, 0, 0, 239, 240, 1, 0, 0, 0, 240, 250, 1, 0, 0, 0, 241, 243, 7, 3, 0, 0, 242, 244, 3, 55, 27, 0, 243, 242, 1, 0, 0, 0, 243, 244, 1, 0, 0, 0, 244, 246, 1, 0, 0, 0, 245, 247, 3, 71, 35, 0, 246, 245, 1, 0, 0, 0, 247, 248, 1, 0, 0, 0, 248, 246, 1, 0, 0, 0, 248, 249, 1, 0, 0, 0, 249, 251, 1, 0, 0, 0, 250, 241, 1, 0, 0, 0, 250, 251, 1, 0, 0, 0, 251, 253, 1, 0, 0, 0, 252, 205, 1, 0, 0, 0, 252, 233, 1, 0, 0, 0, 253, 58, 1, 0, 0, 0, 254, 260, 5, 34, 0, 0, 255, 259, 8, 22, 0, 0, 256, 257, 5, 92, 0, 0, 257, 259, 9, 0, 0, 0, 258, 255, 1, 0, 0, 0, 258, 256, 1, 0, 0, 0, 259, 262, 1, 0, 0, 0, 260, 258, 1, 0, 0, 0, 260, 261, 1, 0, 0, 0, 261, 263, 1, 0, 0, 0, 262, 260, 1, 0, 0, 0, 263, 275, 5, 34, 0, 0, 264, 270, 5, 39, 0, 0, 265, 269, 8, 23, 0, 0, 266, 267, 5, 92, 0, 0, 267, 269, 9, 0, 0, 0, 268, 265, 1, 0, 0, 0, 268, 266, 1, 0, 0, 0, 269, 272, 1, 0, 0, 0, 270, 268, 1, 0, 0, 0, 270, 271, 1, 0, 0, 0, 271, 273, 1, 0, 0, 0, 272, 270, 1, 0, 0, 0, 273, 275, 5, 39, 0, 0, 274, 254, 1, 0, 0, 0, 274, 264, 1, 0, 0, 0, 275, 60, 1, 0, 0, 0, 276, 280, 7, 24, 0, 0, 277, 279, 7, 25, 0, 0, 278, 277, 1, 0, 0, 0, 279, 282, 1, 0, 0, 0, 280, 278, 1, 0, 0, 0, 280, 281, 1, 0, 0, 0, 281, 62, 1, 0, 0, 0, 282, 280, 1, 0, 0, 0, 283, 284, 5, 91, 0, 0, 284, 285, 5, 93, 0, 0, 285, 64, 1, 0, 0, 0, 286, 287, 5, 91, 0, 0, 287, 288, 5, 42, 0, 0, 288, 289, 5, 93, 0, 0, 289, 66, 1, 0, 0, 0, 290, 303, 3, 61, 30, 0, 291, 292, 5, 46, 0, 0, 292, 302, 3, 61, 30, 0, 293, 302, 3, 63, 31, 0, 294, 302, 3, 65, 32, 0, 295, 297, 5, 46, 0, 0, 296, 298, 3, 71, 35, 0, 297, 296, 1, 0, 0, 0, 298, 299, 1, 0, 0, 0, 299, 297, 1, 0, 0, 0, 299, 300, 1, 0, 0, 0, 300, 302, 1, 0, 0, 0, 301, 291, 1, 0, 0, 0, 301, 293, 1, 0, 0, 0, 301, 294, 1, 0, 0, 0, 301, 295, 1, 0, 0, 0, 302, 305, 1, 0, 0, 0, 303, 301, 1, 0, 0, 0, 303, 304, 1, 0, 0, 0, 304, 68, 1, 0, 0, 0, 305, 303, 1, 0, 0, 0, 306, 308, 7, 26, 0, 0, 307, 306, 1, 0, 0, 0, 308, 309, 1, 0, 0, 0, 309, 307, 1, 0, 0, 0, 309, 310, 1, 0, 0, 0, 310, 311, 1, 0, 0, 0, 311, 312, 6, 34, 0, 0, 312, 70, 1, 0, 0, 0, 313, 314, 7, 27, 0, 0, 314, 72, 1, 0, 0, 0, 315, 317, 8, 28, 0, 0, 316, 315, 1, 0, 0, 0, 317, 318, 1, 0, 0, 0, 318, 316, 1, 0, 0, 0, 318, 319, 1, 0, 0, 0, 319, 74, 1, 0, 0, 0, 29, 0, 88, 131, 148, 200, 205, 210, 216, 219, 223, 228, 230, 233, 239, 243, 248, 250, 252, 258, 260, 268, 270, 274, 280, 299, 301, 303, 309, 318, 1, 6, 0, 0] \ No newline at end of file diff --git a/pkg/parser/grammar/filterquery_lexer.go b/pkg/parser/grammar/filterquery_lexer.go index 88f94ec69c24..ac770955d730 100644 --- a/pkg/parser/grammar/filterquery_lexer.go +++ b/pkg/parser/grammar/filterquery_lexer.go @@ -61,7 +61,7 @@ func filterquerylexerLexerInit() { } staticData.PredictionContextCache = antlr.NewPredictionContextCache() staticData.serializedATN = []int32{ - 4, 0, 32, 314, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, + 4, 0, 32, 320, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, @@ -90,127 +90,130 @@ func filterquerylexerLexerInit() { 8, 29, 10, 29, 12, 29, 262, 9, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 5, 29, 269, 8, 29, 10, 29, 12, 29, 272, 9, 29, 1, 29, 3, 29, 275, 8, 29, 1, 30, 1, 30, 5, 30, 279, 8, 30, 10, 30, 12, 30, 282, 9, 30, 1, 31, 1, 31, - 1, 31, 1, 32, 1, 32, 1, 32, 1, 32, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 5, - 33, 296, 8, 33, 10, 33, 12, 33, 299, 9, 33, 1, 34, 4, 34, 302, 8, 34, 11, - 34, 12, 34, 303, 1, 34, 1, 34, 1, 35, 1, 35, 1, 36, 4, 36, 311, 8, 36, - 11, 36, 12, 36, 312, 0, 0, 37, 1, 1, 3, 2, 5, 3, 7, 4, 9, 5, 11, 6, 13, - 7, 15, 8, 17, 9, 19, 10, 21, 11, 23, 12, 25, 13, 27, 14, 29, 15, 31, 16, - 33, 17, 35, 18, 37, 19, 39, 20, 41, 21, 43, 22, 45, 23, 47, 24, 49, 25, - 51, 26, 53, 27, 55, 0, 57, 28, 59, 29, 61, 0, 63, 0, 65, 0, 67, 30, 69, - 31, 71, 0, 73, 32, 1, 0, 29, 2, 0, 76, 76, 108, 108, 2, 0, 73, 73, 105, - 105, 2, 0, 75, 75, 107, 107, 2, 0, 69, 69, 101, 101, 2, 0, 66, 66, 98, - 98, 2, 0, 84, 84, 116, 116, 2, 0, 87, 87, 119, 119, 2, 0, 78, 78, 110, - 110, 2, 0, 88, 88, 120, 120, 2, 0, 83, 83, 115, 115, 2, 0, 82, 82, 114, - 114, 2, 0, 71, 71, 103, 103, 2, 0, 80, 80, 112, 112, 2, 0, 67, 67, 99, - 99, 2, 0, 79, 79, 111, 111, 2, 0, 65, 65, 97, 97, 2, 0, 68, 68, 100, 100, - 2, 0, 72, 72, 104, 104, 2, 0, 89, 89, 121, 121, 2, 0, 85, 85, 117, 117, - 2, 0, 70, 70, 102, 102, 2, 0, 43, 43, 45, 45, 2, 0, 34, 34, 92, 92, 2, - 0, 39, 39, 92, 92, 4, 0, 36, 36, 65, 90, 95, 95, 97, 122, 6, 0, 36, 36, - 45, 45, 47, 58, 65, 90, 95, 95, 97, 122, 3, 0, 9, 10, 13, 13, 32, 32, 1, - 0, 48, 57, 8, 0, 9, 10, 13, 13, 32, 34, 39, 41, 44, 44, 60, 62, 91, 91, - 93, 93, 336, 0, 1, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 5, 1, 0, 0, 0, 0, 7, - 1, 0, 0, 0, 0, 9, 1, 0, 0, 0, 0, 11, 1, 0, 0, 0, 0, 13, 1, 0, 0, 0, 0, - 15, 1, 0, 0, 0, 0, 17, 1, 0, 0, 0, 0, 19, 1, 0, 0, 0, 0, 21, 1, 0, 0, 0, - 0, 23, 1, 0, 0, 0, 0, 25, 1, 0, 0, 0, 0, 27, 1, 0, 0, 0, 0, 29, 1, 0, 0, - 0, 0, 31, 1, 0, 0, 0, 0, 33, 1, 0, 0, 0, 0, 35, 1, 0, 0, 0, 0, 37, 1, 0, - 0, 0, 0, 39, 1, 0, 0, 0, 0, 41, 1, 0, 0, 0, 0, 43, 1, 0, 0, 0, 0, 45, 1, - 0, 0, 0, 0, 47, 1, 0, 0, 0, 0, 49, 1, 0, 0, 0, 0, 51, 1, 0, 0, 0, 0, 53, - 1, 0, 0, 0, 0, 57, 1, 0, 0, 0, 0, 59, 1, 0, 0, 0, 0, 67, 1, 0, 0, 0, 0, - 69, 1, 0, 0, 0, 0, 73, 1, 0, 0, 0, 1, 75, 1, 0, 0, 0, 3, 77, 1, 0, 0, 0, - 5, 79, 1, 0, 0, 0, 7, 81, 1, 0, 0, 0, 9, 83, 1, 0, 0, 0, 11, 88, 1, 0, - 0, 0, 13, 90, 1, 0, 0, 0, 15, 93, 1, 0, 0, 0, 17, 96, 1, 0, 0, 0, 19, 98, - 1, 0, 0, 0, 21, 101, 1, 0, 0, 0, 23, 103, 1, 0, 0, 0, 25, 106, 1, 0, 0, - 0, 27, 111, 1, 0, 0, 0, 29, 117, 1, 0, 0, 0, 31, 125, 1, 0, 0, 0, 33, 133, - 1, 0, 0, 0, 35, 140, 1, 0, 0, 0, 37, 150, 1, 0, 0, 0, 39, 153, 1, 0, 0, - 0, 41, 157, 1, 0, 0, 0, 43, 161, 1, 0, 0, 0, 45, 164, 1, 0, 0, 0, 47, 173, - 1, 0, 0, 0, 49, 177, 1, 0, 0, 0, 51, 184, 1, 0, 0, 0, 53, 200, 1, 0, 0, - 0, 55, 202, 1, 0, 0, 0, 57, 252, 1, 0, 0, 0, 59, 274, 1, 0, 0, 0, 61, 276, - 1, 0, 0, 0, 63, 283, 1, 0, 0, 0, 65, 286, 1, 0, 0, 0, 67, 290, 1, 0, 0, - 0, 69, 301, 1, 0, 0, 0, 71, 307, 1, 0, 0, 0, 73, 310, 1, 0, 0, 0, 75, 76, - 5, 40, 0, 0, 76, 2, 1, 0, 0, 0, 77, 78, 5, 41, 0, 0, 78, 4, 1, 0, 0, 0, - 79, 80, 5, 91, 0, 0, 80, 6, 1, 0, 0, 0, 81, 82, 5, 93, 0, 0, 82, 8, 1, - 0, 0, 0, 83, 84, 5, 44, 0, 0, 84, 10, 1, 0, 0, 0, 85, 89, 5, 61, 0, 0, - 86, 87, 5, 61, 0, 0, 87, 89, 5, 61, 0, 0, 88, 85, 1, 0, 0, 0, 88, 86, 1, - 0, 0, 0, 89, 12, 1, 0, 0, 0, 90, 91, 5, 33, 0, 0, 91, 92, 5, 61, 0, 0, - 92, 14, 1, 0, 0, 0, 93, 94, 5, 60, 0, 0, 94, 95, 5, 62, 0, 0, 95, 16, 1, - 0, 0, 0, 96, 97, 5, 60, 0, 0, 97, 18, 1, 0, 0, 0, 98, 99, 5, 60, 0, 0, - 99, 100, 5, 61, 0, 0, 100, 20, 1, 0, 0, 0, 101, 102, 5, 62, 0, 0, 102, - 22, 1, 0, 0, 0, 103, 104, 5, 62, 0, 0, 104, 105, 5, 61, 0, 0, 105, 24, - 1, 0, 0, 0, 106, 107, 7, 0, 0, 0, 107, 108, 7, 1, 0, 0, 108, 109, 7, 2, - 0, 0, 109, 110, 7, 3, 0, 0, 110, 26, 1, 0, 0, 0, 111, 112, 7, 1, 0, 0, - 112, 113, 7, 0, 0, 0, 113, 114, 7, 1, 0, 0, 114, 115, 7, 2, 0, 0, 115, - 116, 7, 3, 0, 0, 116, 28, 1, 0, 0, 0, 117, 118, 7, 4, 0, 0, 118, 119, 7, - 3, 0, 0, 119, 120, 7, 5, 0, 0, 120, 121, 7, 6, 0, 0, 121, 122, 7, 3, 0, - 0, 122, 123, 7, 3, 0, 0, 123, 124, 7, 7, 0, 0, 124, 30, 1, 0, 0, 0, 125, - 126, 7, 3, 0, 0, 126, 127, 7, 8, 0, 0, 127, 128, 7, 1, 0, 0, 128, 129, - 7, 9, 0, 0, 129, 131, 7, 5, 0, 0, 130, 132, 7, 9, 0, 0, 131, 130, 1, 0, - 0, 0, 131, 132, 1, 0, 0, 0, 132, 32, 1, 0, 0, 0, 133, 134, 7, 10, 0, 0, - 134, 135, 7, 3, 0, 0, 135, 136, 7, 11, 0, 0, 136, 137, 7, 3, 0, 0, 137, - 138, 7, 8, 0, 0, 138, 139, 7, 12, 0, 0, 139, 34, 1, 0, 0, 0, 140, 141, - 7, 13, 0, 0, 141, 142, 7, 14, 0, 0, 142, 143, 7, 7, 0, 0, 143, 144, 7, - 5, 0, 0, 144, 145, 7, 15, 0, 0, 145, 146, 7, 1, 0, 0, 146, 148, 7, 7, 0, - 0, 147, 149, 7, 9, 0, 0, 148, 147, 1, 0, 0, 0, 148, 149, 1, 0, 0, 0, 149, - 36, 1, 0, 0, 0, 150, 151, 7, 1, 0, 0, 151, 152, 7, 7, 0, 0, 152, 38, 1, - 0, 0, 0, 153, 154, 7, 7, 0, 0, 154, 155, 7, 14, 0, 0, 155, 156, 7, 5, 0, - 0, 156, 40, 1, 0, 0, 0, 157, 158, 7, 15, 0, 0, 158, 159, 7, 7, 0, 0, 159, - 160, 7, 16, 0, 0, 160, 42, 1, 0, 0, 0, 161, 162, 7, 14, 0, 0, 162, 163, - 7, 10, 0, 0, 163, 44, 1, 0, 0, 0, 164, 165, 7, 17, 0, 0, 165, 166, 7, 15, - 0, 0, 166, 167, 7, 9, 0, 0, 167, 168, 7, 5, 0, 0, 168, 169, 7, 14, 0, 0, - 169, 170, 7, 2, 0, 0, 170, 171, 7, 3, 0, 0, 171, 172, 7, 7, 0, 0, 172, - 46, 1, 0, 0, 0, 173, 174, 7, 17, 0, 0, 174, 175, 7, 15, 0, 0, 175, 176, - 7, 9, 0, 0, 176, 48, 1, 0, 0, 0, 177, 178, 7, 17, 0, 0, 178, 179, 7, 15, - 0, 0, 179, 180, 7, 9, 0, 0, 180, 181, 7, 15, 0, 0, 181, 182, 7, 7, 0, 0, - 182, 183, 7, 18, 0, 0, 183, 50, 1, 0, 0, 0, 184, 185, 7, 17, 0, 0, 185, - 186, 7, 15, 0, 0, 186, 187, 7, 9, 0, 0, 187, 188, 7, 15, 0, 0, 188, 189, - 7, 0, 0, 0, 189, 190, 7, 0, 0, 0, 190, 52, 1, 0, 0, 0, 191, 192, 7, 5, - 0, 0, 192, 193, 7, 10, 0, 0, 193, 194, 7, 19, 0, 0, 194, 201, 7, 3, 0, - 0, 195, 196, 7, 20, 0, 0, 196, 197, 7, 15, 0, 0, 197, 198, 7, 0, 0, 0, - 198, 199, 7, 9, 0, 0, 199, 201, 7, 3, 0, 0, 200, 191, 1, 0, 0, 0, 200, - 195, 1, 0, 0, 0, 201, 54, 1, 0, 0, 0, 202, 203, 7, 21, 0, 0, 203, 56, 1, - 0, 0, 0, 204, 206, 3, 55, 27, 0, 205, 204, 1, 0, 0, 0, 205, 206, 1, 0, - 0, 0, 206, 208, 1, 0, 0, 0, 207, 209, 3, 71, 35, 0, 208, 207, 1, 0, 0, - 0, 209, 210, 1, 0, 0, 0, 210, 208, 1, 0, 0, 0, 210, 211, 1, 0, 0, 0, 211, - 219, 1, 0, 0, 0, 212, 216, 5, 46, 0, 0, 213, 215, 3, 71, 35, 0, 214, 213, - 1, 0, 0, 0, 215, 218, 1, 0, 0, 0, 216, 214, 1, 0, 0, 0, 216, 217, 1, 0, - 0, 0, 217, 220, 1, 0, 0, 0, 218, 216, 1, 0, 0, 0, 219, 212, 1, 0, 0, 0, - 219, 220, 1, 0, 0, 0, 220, 230, 1, 0, 0, 0, 221, 223, 7, 3, 0, 0, 222, - 224, 3, 55, 27, 0, 223, 222, 1, 0, 0, 0, 223, 224, 1, 0, 0, 0, 224, 226, - 1, 0, 0, 0, 225, 227, 3, 71, 35, 0, 226, 225, 1, 0, 0, 0, 227, 228, 1, - 0, 0, 0, 228, 226, 1, 0, 0, 0, 228, 229, 1, 0, 0, 0, 229, 231, 1, 0, 0, - 0, 230, 221, 1, 0, 0, 0, 230, 231, 1, 0, 0, 0, 231, 253, 1, 0, 0, 0, 232, - 234, 3, 55, 27, 0, 233, 232, 1, 0, 0, 0, 233, 234, 1, 0, 0, 0, 234, 235, - 1, 0, 0, 0, 235, 237, 5, 46, 0, 0, 236, 238, 3, 71, 35, 0, 237, 236, 1, - 0, 0, 0, 238, 239, 1, 0, 0, 0, 239, 237, 1, 0, 0, 0, 239, 240, 1, 0, 0, - 0, 240, 250, 1, 0, 0, 0, 241, 243, 7, 3, 0, 0, 242, 244, 3, 55, 27, 0, - 243, 242, 1, 0, 0, 0, 243, 244, 1, 0, 0, 0, 244, 246, 1, 0, 0, 0, 245, - 247, 3, 71, 35, 0, 246, 245, 1, 0, 0, 0, 247, 248, 1, 0, 0, 0, 248, 246, - 1, 0, 0, 0, 248, 249, 1, 0, 0, 0, 249, 251, 1, 0, 0, 0, 250, 241, 1, 0, - 0, 0, 250, 251, 1, 0, 0, 0, 251, 253, 1, 0, 0, 0, 252, 205, 1, 0, 0, 0, - 252, 233, 1, 0, 0, 0, 253, 58, 1, 0, 0, 0, 254, 260, 5, 34, 0, 0, 255, - 259, 8, 22, 0, 0, 256, 257, 5, 92, 0, 0, 257, 259, 9, 0, 0, 0, 258, 255, - 1, 0, 0, 0, 258, 256, 1, 0, 0, 0, 259, 262, 1, 0, 0, 0, 260, 258, 1, 0, - 0, 0, 260, 261, 1, 0, 0, 0, 261, 263, 1, 0, 0, 0, 262, 260, 1, 0, 0, 0, - 263, 275, 5, 34, 0, 0, 264, 270, 5, 39, 0, 0, 265, 269, 8, 23, 0, 0, 266, - 267, 5, 92, 0, 0, 267, 269, 9, 0, 0, 0, 268, 265, 1, 0, 0, 0, 268, 266, - 1, 0, 0, 0, 269, 272, 1, 0, 0, 0, 270, 268, 1, 0, 0, 0, 270, 271, 1, 0, - 0, 0, 271, 273, 1, 0, 0, 0, 272, 270, 1, 0, 0, 0, 273, 275, 5, 39, 0, 0, - 274, 254, 1, 0, 0, 0, 274, 264, 1, 0, 0, 0, 275, 60, 1, 0, 0, 0, 276, 280, - 7, 24, 0, 0, 277, 279, 7, 25, 0, 0, 278, 277, 1, 0, 0, 0, 279, 282, 1, - 0, 0, 0, 280, 278, 1, 0, 0, 0, 280, 281, 1, 0, 0, 0, 281, 62, 1, 0, 0, - 0, 282, 280, 1, 0, 0, 0, 283, 284, 5, 91, 0, 0, 284, 285, 5, 93, 0, 0, - 285, 64, 1, 0, 0, 0, 286, 287, 5, 91, 0, 0, 287, 288, 5, 42, 0, 0, 288, - 289, 5, 93, 0, 0, 289, 66, 1, 0, 0, 0, 290, 297, 3, 61, 30, 0, 291, 292, - 5, 46, 0, 0, 292, 296, 3, 61, 30, 0, 293, 296, 3, 63, 31, 0, 294, 296, - 3, 65, 32, 0, 295, 291, 1, 0, 0, 0, 295, 293, 1, 0, 0, 0, 295, 294, 1, - 0, 0, 0, 296, 299, 1, 0, 0, 0, 297, 295, 1, 0, 0, 0, 297, 298, 1, 0, 0, - 0, 298, 68, 1, 0, 0, 0, 299, 297, 1, 0, 0, 0, 300, 302, 7, 26, 0, 0, 301, - 300, 1, 0, 0, 0, 302, 303, 1, 0, 0, 0, 303, 301, 1, 0, 0, 0, 303, 304, - 1, 0, 0, 0, 304, 305, 1, 0, 0, 0, 305, 306, 6, 34, 0, 0, 306, 70, 1, 0, - 0, 0, 307, 308, 7, 27, 0, 0, 308, 72, 1, 0, 0, 0, 309, 311, 8, 28, 0, 0, - 310, 309, 1, 0, 0, 0, 311, 312, 1, 0, 0, 0, 312, 310, 1, 0, 0, 0, 312, - 313, 1, 0, 0, 0, 313, 74, 1, 0, 0, 0, 28, 0, 88, 131, 148, 200, 205, 210, - 216, 219, 223, 228, 230, 233, 239, 243, 248, 250, 252, 258, 260, 268, 270, - 274, 280, 295, 297, 303, 312, 1, 6, 0, 0, + 1, 31, 1, 32, 1, 32, 1, 32, 1, 32, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, + 33, 1, 33, 4, 33, 298, 8, 33, 11, 33, 12, 33, 299, 5, 33, 302, 8, 33, 10, + 33, 12, 33, 305, 9, 33, 1, 34, 4, 34, 308, 8, 34, 11, 34, 12, 34, 309, + 1, 34, 1, 34, 1, 35, 1, 35, 1, 36, 4, 36, 317, 8, 36, 11, 36, 12, 36, 318, + 0, 0, 37, 1, 1, 3, 2, 5, 3, 7, 4, 9, 5, 11, 6, 13, 7, 15, 8, 17, 9, 19, + 10, 21, 11, 23, 12, 25, 13, 27, 14, 29, 15, 31, 16, 33, 17, 35, 18, 37, + 19, 39, 20, 41, 21, 43, 22, 45, 23, 47, 24, 49, 25, 51, 26, 53, 27, 55, + 0, 57, 28, 59, 29, 61, 0, 63, 0, 65, 0, 67, 30, 69, 31, 71, 0, 73, 32, + 1, 0, 29, 2, 0, 76, 76, 108, 108, 2, 0, 73, 73, 105, 105, 2, 0, 75, 75, + 107, 107, 2, 0, 69, 69, 101, 101, 2, 0, 66, 66, 98, 98, 2, 0, 84, 84, 116, + 116, 2, 0, 87, 87, 119, 119, 2, 0, 78, 78, 110, 110, 2, 0, 88, 88, 120, + 120, 2, 0, 83, 83, 115, 115, 2, 0, 82, 82, 114, 114, 2, 0, 71, 71, 103, + 103, 2, 0, 80, 80, 112, 112, 2, 0, 67, 67, 99, 99, 2, 0, 79, 79, 111, 111, + 2, 0, 65, 65, 97, 97, 2, 0, 68, 68, 100, 100, 2, 0, 72, 72, 104, 104, 2, + 0, 89, 89, 121, 121, 2, 0, 85, 85, 117, 117, 2, 0, 70, 70, 102, 102, 2, + 0, 43, 43, 45, 45, 2, 0, 34, 34, 92, 92, 2, 0, 39, 39, 92, 92, 4, 0, 35, + 36, 64, 90, 95, 95, 97, 123, 7, 0, 35, 36, 45, 45, 47, 58, 64, 90, 95, + 95, 97, 123, 125, 125, 3, 0, 9, 10, 13, 13, 32, 32, 1, 0, 48, 57, 8, 0, + 9, 10, 13, 13, 32, 34, 39, 41, 44, 44, 60, 62, 91, 91, 93, 93, 344, 0, + 1, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 5, 1, 0, 0, 0, 0, 7, 1, 0, 0, 0, 0, + 9, 1, 0, 0, 0, 0, 11, 1, 0, 0, 0, 0, 13, 1, 0, 0, 0, 0, 15, 1, 0, 0, 0, + 0, 17, 1, 0, 0, 0, 0, 19, 1, 0, 0, 0, 0, 21, 1, 0, 0, 0, 0, 23, 1, 0, 0, + 0, 0, 25, 1, 0, 0, 0, 0, 27, 1, 0, 0, 0, 0, 29, 1, 0, 0, 0, 0, 31, 1, 0, + 0, 0, 0, 33, 1, 0, 0, 0, 0, 35, 1, 0, 0, 0, 0, 37, 1, 0, 0, 0, 0, 39, 1, + 0, 0, 0, 0, 41, 1, 0, 0, 0, 0, 43, 1, 0, 0, 0, 0, 45, 1, 0, 0, 0, 0, 47, + 1, 0, 0, 0, 0, 49, 1, 0, 0, 0, 0, 51, 1, 0, 0, 0, 0, 53, 1, 0, 0, 0, 0, + 57, 1, 0, 0, 0, 0, 59, 1, 0, 0, 0, 0, 67, 1, 0, 0, 0, 0, 69, 1, 0, 0, 0, + 0, 73, 1, 0, 0, 0, 1, 75, 1, 0, 0, 0, 3, 77, 1, 0, 0, 0, 5, 79, 1, 0, 0, + 0, 7, 81, 1, 0, 0, 0, 9, 83, 1, 0, 0, 0, 11, 88, 1, 0, 0, 0, 13, 90, 1, + 0, 0, 0, 15, 93, 1, 0, 0, 0, 17, 96, 1, 0, 0, 0, 19, 98, 1, 0, 0, 0, 21, + 101, 1, 0, 0, 0, 23, 103, 1, 0, 0, 0, 25, 106, 1, 0, 0, 0, 27, 111, 1, + 0, 0, 0, 29, 117, 1, 0, 0, 0, 31, 125, 1, 0, 0, 0, 33, 133, 1, 0, 0, 0, + 35, 140, 1, 0, 0, 0, 37, 150, 1, 0, 0, 0, 39, 153, 1, 0, 0, 0, 41, 157, + 1, 0, 0, 0, 43, 161, 1, 0, 0, 0, 45, 164, 1, 0, 0, 0, 47, 173, 1, 0, 0, + 0, 49, 177, 1, 0, 0, 0, 51, 184, 1, 0, 0, 0, 53, 200, 1, 0, 0, 0, 55, 202, + 1, 0, 0, 0, 57, 252, 1, 0, 0, 0, 59, 274, 1, 0, 0, 0, 61, 276, 1, 0, 0, + 0, 63, 283, 1, 0, 0, 0, 65, 286, 1, 0, 0, 0, 67, 290, 1, 0, 0, 0, 69, 307, + 1, 0, 0, 0, 71, 313, 1, 0, 0, 0, 73, 316, 1, 0, 0, 0, 75, 76, 5, 40, 0, + 0, 76, 2, 1, 0, 0, 0, 77, 78, 5, 41, 0, 0, 78, 4, 1, 0, 0, 0, 79, 80, 5, + 91, 0, 0, 80, 6, 1, 0, 0, 0, 81, 82, 5, 93, 0, 0, 82, 8, 1, 0, 0, 0, 83, + 84, 5, 44, 0, 0, 84, 10, 1, 0, 0, 0, 85, 89, 5, 61, 0, 0, 86, 87, 5, 61, + 0, 0, 87, 89, 5, 61, 0, 0, 88, 85, 1, 0, 0, 0, 88, 86, 1, 0, 0, 0, 89, + 12, 1, 0, 0, 0, 90, 91, 5, 33, 0, 0, 91, 92, 5, 61, 0, 0, 92, 14, 1, 0, + 0, 0, 93, 94, 5, 60, 0, 0, 94, 95, 5, 62, 0, 0, 95, 16, 1, 0, 0, 0, 96, + 97, 5, 60, 0, 0, 97, 18, 1, 0, 0, 0, 98, 99, 5, 60, 0, 0, 99, 100, 5, 61, + 0, 0, 100, 20, 1, 0, 0, 0, 101, 102, 5, 62, 0, 0, 102, 22, 1, 0, 0, 0, + 103, 104, 5, 62, 0, 0, 104, 105, 5, 61, 0, 0, 105, 24, 1, 0, 0, 0, 106, + 107, 7, 0, 0, 0, 107, 108, 7, 1, 0, 0, 108, 109, 7, 2, 0, 0, 109, 110, + 7, 3, 0, 0, 110, 26, 1, 0, 0, 0, 111, 112, 7, 1, 0, 0, 112, 113, 7, 0, + 0, 0, 113, 114, 7, 1, 0, 0, 114, 115, 7, 2, 0, 0, 115, 116, 7, 3, 0, 0, + 116, 28, 1, 0, 0, 0, 117, 118, 7, 4, 0, 0, 118, 119, 7, 3, 0, 0, 119, 120, + 7, 5, 0, 0, 120, 121, 7, 6, 0, 0, 121, 122, 7, 3, 0, 0, 122, 123, 7, 3, + 0, 0, 123, 124, 7, 7, 0, 0, 124, 30, 1, 0, 0, 0, 125, 126, 7, 3, 0, 0, + 126, 127, 7, 8, 0, 0, 127, 128, 7, 1, 0, 0, 128, 129, 7, 9, 0, 0, 129, + 131, 7, 5, 0, 0, 130, 132, 7, 9, 0, 0, 131, 130, 1, 0, 0, 0, 131, 132, + 1, 0, 0, 0, 132, 32, 1, 0, 0, 0, 133, 134, 7, 10, 0, 0, 134, 135, 7, 3, + 0, 0, 135, 136, 7, 11, 0, 0, 136, 137, 7, 3, 0, 0, 137, 138, 7, 8, 0, 0, + 138, 139, 7, 12, 0, 0, 139, 34, 1, 0, 0, 0, 140, 141, 7, 13, 0, 0, 141, + 142, 7, 14, 0, 0, 142, 143, 7, 7, 0, 0, 143, 144, 7, 5, 0, 0, 144, 145, + 7, 15, 0, 0, 145, 146, 7, 1, 0, 0, 146, 148, 7, 7, 0, 0, 147, 149, 7, 9, + 0, 0, 148, 147, 1, 0, 0, 0, 148, 149, 1, 0, 0, 0, 149, 36, 1, 0, 0, 0, + 150, 151, 7, 1, 0, 0, 151, 152, 7, 7, 0, 0, 152, 38, 1, 0, 0, 0, 153, 154, + 7, 7, 0, 0, 154, 155, 7, 14, 0, 0, 155, 156, 7, 5, 0, 0, 156, 40, 1, 0, + 0, 0, 157, 158, 7, 15, 0, 0, 158, 159, 7, 7, 0, 0, 159, 160, 7, 16, 0, + 0, 160, 42, 1, 0, 0, 0, 161, 162, 7, 14, 0, 0, 162, 163, 7, 10, 0, 0, 163, + 44, 1, 0, 0, 0, 164, 165, 7, 17, 0, 0, 165, 166, 7, 15, 0, 0, 166, 167, + 7, 9, 0, 0, 167, 168, 7, 5, 0, 0, 168, 169, 7, 14, 0, 0, 169, 170, 7, 2, + 0, 0, 170, 171, 7, 3, 0, 0, 171, 172, 7, 7, 0, 0, 172, 46, 1, 0, 0, 0, + 173, 174, 7, 17, 0, 0, 174, 175, 7, 15, 0, 0, 175, 176, 7, 9, 0, 0, 176, + 48, 1, 0, 0, 0, 177, 178, 7, 17, 0, 0, 178, 179, 7, 15, 0, 0, 179, 180, + 7, 9, 0, 0, 180, 181, 7, 15, 0, 0, 181, 182, 7, 7, 0, 0, 182, 183, 7, 18, + 0, 0, 183, 50, 1, 0, 0, 0, 184, 185, 7, 17, 0, 0, 185, 186, 7, 15, 0, 0, + 186, 187, 7, 9, 0, 0, 187, 188, 7, 15, 0, 0, 188, 189, 7, 0, 0, 0, 189, + 190, 7, 0, 0, 0, 190, 52, 1, 0, 0, 0, 191, 192, 7, 5, 0, 0, 192, 193, 7, + 10, 0, 0, 193, 194, 7, 19, 0, 0, 194, 201, 7, 3, 0, 0, 195, 196, 7, 20, + 0, 0, 196, 197, 7, 15, 0, 0, 197, 198, 7, 0, 0, 0, 198, 199, 7, 9, 0, 0, + 199, 201, 7, 3, 0, 0, 200, 191, 1, 0, 0, 0, 200, 195, 1, 0, 0, 0, 201, + 54, 1, 0, 0, 0, 202, 203, 7, 21, 0, 0, 203, 56, 1, 0, 0, 0, 204, 206, 3, + 55, 27, 0, 205, 204, 1, 0, 0, 0, 205, 206, 1, 0, 0, 0, 206, 208, 1, 0, + 0, 0, 207, 209, 3, 71, 35, 0, 208, 207, 1, 0, 0, 0, 209, 210, 1, 0, 0, + 0, 210, 208, 1, 0, 0, 0, 210, 211, 1, 0, 0, 0, 211, 219, 1, 0, 0, 0, 212, + 216, 5, 46, 0, 0, 213, 215, 3, 71, 35, 0, 214, 213, 1, 0, 0, 0, 215, 218, + 1, 0, 0, 0, 216, 214, 1, 0, 0, 0, 216, 217, 1, 0, 0, 0, 217, 220, 1, 0, + 0, 0, 218, 216, 1, 0, 0, 0, 219, 212, 1, 0, 0, 0, 219, 220, 1, 0, 0, 0, + 220, 230, 1, 0, 0, 0, 221, 223, 7, 3, 0, 0, 222, 224, 3, 55, 27, 0, 223, + 222, 1, 0, 0, 0, 223, 224, 1, 0, 0, 0, 224, 226, 1, 0, 0, 0, 225, 227, + 3, 71, 35, 0, 226, 225, 1, 0, 0, 0, 227, 228, 1, 0, 0, 0, 228, 226, 1, + 0, 0, 0, 228, 229, 1, 0, 0, 0, 229, 231, 1, 0, 0, 0, 230, 221, 1, 0, 0, + 0, 230, 231, 1, 0, 0, 0, 231, 253, 1, 0, 0, 0, 232, 234, 3, 55, 27, 0, + 233, 232, 1, 0, 0, 0, 233, 234, 1, 0, 0, 0, 234, 235, 1, 0, 0, 0, 235, + 237, 5, 46, 0, 0, 236, 238, 3, 71, 35, 0, 237, 236, 1, 0, 0, 0, 238, 239, + 1, 0, 0, 0, 239, 237, 1, 0, 0, 0, 239, 240, 1, 0, 0, 0, 240, 250, 1, 0, + 0, 0, 241, 243, 7, 3, 0, 0, 242, 244, 3, 55, 27, 0, 243, 242, 1, 0, 0, + 0, 243, 244, 1, 0, 0, 0, 244, 246, 1, 0, 0, 0, 245, 247, 3, 71, 35, 0, + 246, 245, 1, 0, 0, 0, 247, 248, 1, 0, 0, 0, 248, 246, 1, 0, 0, 0, 248, + 249, 1, 0, 0, 0, 249, 251, 1, 0, 0, 0, 250, 241, 1, 0, 0, 0, 250, 251, + 1, 0, 0, 0, 251, 253, 1, 0, 0, 0, 252, 205, 1, 0, 0, 0, 252, 233, 1, 0, + 0, 0, 253, 58, 1, 0, 0, 0, 254, 260, 5, 34, 0, 0, 255, 259, 8, 22, 0, 0, + 256, 257, 5, 92, 0, 0, 257, 259, 9, 0, 0, 0, 258, 255, 1, 0, 0, 0, 258, + 256, 1, 0, 0, 0, 259, 262, 1, 0, 0, 0, 260, 258, 1, 0, 0, 0, 260, 261, + 1, 0, 0, 0, 261, 263, 1, 0, 0, 0, 262, 260, 1, 0, 0, 0, 263, 275, 5, 34, + 0, 0, 264, 270, 5, 39, 0, 0, 265, 269, 8, 23, 0, 0, 266, 267, 5, 92, 0, + 0, 267, 269, 9, 0, 0, 0, 268, 265, 1, 0, 0, 0, 268, 266, 1, 0, 0, 0, 269, + 272, 1, 0, 0, 0, 270, 268, 1, 0, 0, 0, 270, 271, 1, 0, 0, 0, 271, 273, + 1, 0, 0, 0, 272, 270, 1, 0, 0, 0, 273, 275, 5, 39, 0, 0, 274, 254, 1, 0, + 0, 0, 274, 264, 1, 0, 0, 0, 275, 60, 1, 0, 0, 0, 276, 280, 7, 24, 0, 0, + 277, 279, 7, 25, 0, 0, 278, 277, 1, 0, 0, 0, 279, 282, 1, 0, 0, 0, 280, + 278, 1, 0, 0, 0, 280, 281, 1, 0, 0, 0, 281, 62, 1, 0, 0, 0, 282, 280, 1, + 0, 0, 0, 283, 284, 5, 91, 0, 0, 284, 285, 5, 93, 0, 0, 285, 64, 1, 0, 0, + 0, 286, 287, 5, 91, 0, 0, 287, 288, 5, 42, 0, 0, 288, 289, 5, 93, 0, 0, + 289, 66, 1, 0, 0, 0, 290, 303, 3, 61, 30, 0, 291, 292, 5, 46, 0, 0, 292, + 302, 3, 61, 30, 0, 293, 302, 3, 63, 31, 0, 294, 302, 3, 65, 32, 0, 295, + 297, 5, 46, 0, 0, 296, 298, 3, 71, 35, 0, 297, 296, 1, 0, 0, 0, 298, 299, + 1, 0, 0, 0, 299, 297, 1, 0, 0, 0, 299, 300, 1, 0, 0, 0, 300, 302, 1, 0, + 0, 0, 301, 291, 1, 0, 0, 0, 301, 293, 1, 0, 0, 0, 301, 294, 1, 0, 0, 0, + 301, 295, 1, 0, 0, 0, 302, 305, 1, 0, 0, 0, 303, 301, 1, 0, 0, 0, 303, + 304, 1, 0, 0, 0, 304, 68, 1, 0, 0, 0, 305, 303, 1, 0, 0, 0, 306, 308, 7, + 26, 0, 0, 307, 306, 1, 0, 0, 0, 308, 309, 1, 0, 0, 0, 309, 307, 1, 0, 0, + 0, 309, 310, 1, 0, 0, 0, 310, 311, 1, 0, 0, 0, 311, 312, 6, 34, 0, 0, 312, + 70, 1, 0, 0, 0, 313, 314, 7, 27, 0, 0, 314, 72, 1, 0, 0, 0, 315, 317, 8, + 28, 0, 0, 316, 315, 1, 0, 0, 0, 317, 318, 1, 0, 0, 0, 318, 316, 1, 0, 0, + 0, 318, 319, 1, 0, 0, 0, 319, 74, 1, 0, 0, 0, 29, 0, 88, 131, 148, 200, + 205, 210, 216, 219, 223, 228, 230, 233, 239, 243, 248, 250, 252, 258, 260, + 268, 270, 274, 280, 299, 301, 303, 309, 318, 1, 6, 0, 0, } deserializer := antlr.NewATNDeserializer(nil) staticData.atn = deserializer.Deserialize(staticData.serializedATN) diff --git a/pkg/telemetrylogs/filter_expr_logs_test.go b/pkg/telemetrylogs/filter_expr_logs_test.go index 2a46a96066f2..3b9a32dd515b 100644 --- a/pkg/telemetrylogs/filter_expr_logs_test.go +++ b/pkg/telemetrylogs/filter_expr_logs_test.go @@ -121,6 +121,38 @@ func TestFilterExprLogs(t *testing.T) { expectedErrorContains: "", }, + { + category: "Key with curly brace", + query: `{UserId} = "U101"`, + shouldPass: true, + expectedQuery: "WHERE (attributes_string['{UserId}'] = ? AND mapContains(attributes_string, '{UserId}') = ?)", + expectedArgs: []any{"U101", true}, + expectedErrorContains: "", + }, + { + category: "Key with @symbol", + query: `user@email = "u@example.com"`, + shouldPass: true, + expectedQuery: "WHERE (attributes_string['user@email'] = ? AND mapContains(attributes_string, 'user@email') = ?)", + expectedArgs: []any{"u@example.com", true}, + expectedErrorContains: "", + }, + { + category: "Key with @symbol", + query: `#user_name = "anon42069"`, + shouldPass: true, + expectedQuery: "WHERE (attributes_string['#user_name'] = ? AND mapContains(attributes_string, '#user_name') = ?)", + expectedArgs: []any{"anon42069", true}, + expectedErrorContains: "", + }, + { + category: "Key with @symbol", + query: `gen_ai.completion.0.content = "जब तक इस देश में सिनेमा है"`, + shouldPass: true, + expectedQuery: "WHERE (attributes_string['gen_ai.completion.0.content'] = ? AND mapContains(attributes_string, 'gen_ai.completion.0.content') = ?)", + expectedArgs: []any{"जब तक इस देश में सिनेमा है", true}, + expectedErrorContains: "", + }, // Searches with special characters { category: "Special characters", @@ -2437,6 +2469,14 @@ func TestFilterExprLogsConflictNegation(t *testing.T) { expectedArgs: []any{"done", "done"}, expectedErrorContains: "", }, + { + category: "exists", + query: "body EXISTS", + shouldPass: true, + expectedQuery: "WHERE (body <> ? OR mapContains(attributes_string, 'body') = ?)", + expectedArgs: []any{"", true}, + expectedErrorContains: "", + }, } for _, tc := range testCases { diff --git a/pkg/telemetrylogs/test_data.go b/pkg/telemetrylogs/test_data.go index 74714361bbd7..3dd05933d9b2 100644 --- a/pkg/telemetrylogs/test_data.go +++ b/pkg/telemetrylogs/test_data.go @@ -862,6 +862,34 @@ func buildCompleteFieldKeyMap() map[string][]*telemetrytypes.TelemetryFieldKey { Materialized: true, }, }, + "{UserId}": { + { + Name: "{UserId}", + FieldContext: telemetrytypes.FieldContextAttribute, + FieldDataType: telemetrytypes.FieldDataTypeString, + }, + }, + "user@email": { + { + Name: "user@email", + FieldContext: telemetrytypes.FieldContextAttribute, + FieldDataType: telemetrytypes.FieldDataTypeString, + }, + }, + "#user_name": { + { + Name: "#user_name", + FieldContext: telemetrytypes.FieldContextAttribute, + FieldDataType: telemetrytypes.FieldDataTypeString, + }, + }, + "gen_ai.completion.0.content": { + { + Name: "gen_ai.completion.0.content", + FieldContext: telemetrytypes.FieldContextAttribute, + FieldDataType: telemetrytypes.FieldDataTypeString, + }, + }, } for _, keys := range keysMap { diff --git a/pkg/types/querybuildertypes/querybuildertypesv5/builder_elements.go b/pkg/types/querybuildertypes/querybuildertypesv5/builder_elements.go index a89fed33ef92..06c4c93a6906 100644 --- a/pkg/types/querybuildertypes/querybuildertypesv5/builder_elements.go +++ b/pkg/types/querybuildertypes/querybuildertypesv5/builder_elements.go @@ -129,6 +129,7 @@ func (f FilterOperator) IsNegativeOperator() bool { FilterOperatorILike, FilterOperatorBetween, FilterOperatorIn, + FilterOperatorExists, FilterOperatorRegexp, FilterOperatorContains: return false diff --git a/scripts/grammar/generate-frontend-parser.sh b/scripts/grammar/generate-frontend-parser.sh new file mode 100755 index 000000000000..9fb933bdef7b --- /dev/null +++ b/scripts/grammar/generate-frontend-parser.sh @@ -0,0 +1,11 @@ +#!/bin/bash +set -e + +echo "Generating TypeScript parser..." +# Create output directory if it doesn't exist +mkdir -p frontend/src/parser + +# Generate TypeScript parser +antlr4 -Dlanguage=TypeScript -o frontend/src/parser grammar/FilterQuery.g4 -visitor + +echo "TypeScript parser generation complete" From ac81eab7bb350b35bce0809a8041bf5217161f37 Mon Sep 17 00:00:00 2001 From: aniketio-ctrl Date: Mon, 15 Sep 2025 15:00:12 +0530 Subject: [PATCH 7/9] chore: added cumulative window support (#8828) * feat(multi-threshold): added multi threshold * Update pkg/types/ruletypes/api_params.go Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> * feat(multiple-threshold): added multiple thresholds * Update pkg/types/ruletypes/alerting.go Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> * feat(multiple-threshold): added multiple thresholds * feat(cumulative-window): added cumulative window * feat(multi-threshold): added recovery min points * Update pkg/query-service/rules/threshold_rule.go Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> * feat(multi-threshold): fixed log lines * feat(multi-threshold): added severity as threshold name * feat(cumulative-window): added cumulative window for alerts v2 * feat(multi-threshold): removed break to send multi threshold alerts * feat(multi-threshold): removed break to send multi threshold alerts * feat(cumulative-window): segregated json marshalling with evaluation logic * feat(multi-threshold): corrected the test cases * feat(cumulative-window): segregated json marshalling and evaluation logic * feat(cumulative-window): segregated json marshalling and evaluation logic * feat(multi-threshold): added segregation on json marshalling and actual threhsold logic * feat(multi-threshold): added segregation on json marshalling and actual threhsold logic * feat(cumulative-window): segregated json marshalling and evaluation logic * feat(multi-threshold): added segregation on json marshalling and actual threhsold logic * feat(cumulative-window): segregated json marshalling and evaluation logic * feat(multi-threhsold): added error wrapper * feat(multi-threhsold): added error wrapper * feat(cumulative-window): segregated json marshalling and evaluation logic * feat(multi-threhsold): added error wrapper * Update pkg/types/ruletypes/threshold.go Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> * feat(cumulative-window): segregated json marshalling and evaluation logic * feat(multi-threshold): added validation and error propagation * feat(multi-notification): removed pre defined labels from links of log and traces * feat(multi-notification): removed pre defined labels from links of log and traces * feat(multi-threshold): added json parser for gettable rule * feat(multi-threshold): added json parser for gettable rule * feat(multi-threshold): added json parser for gettable rule * feat(multi-threshold): added umnarshaller for postable rule * feat(multi-threshold): added umnarshaller for postable rule * feat(cumulative-window): added validation check * Update pkg/types/ruletypes/evaluation.go Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> * feat(multi-threhsold): removed yaml support for alerts * Update pkg/types/ruletypes/evaluation.go Co-authored-by: Srikanth Chekuri * Update pkg/types/ruletypes/evaluation.go Co-authored-by: Srikanth Chekuri * chore(cumulative-window): renamed funcitons * chore(cumulative-window): removed naked errors * chore(cumulative-window): added reset boundary condition tests * chore(cumulative-window): added reset boundary condition tests * chore(cumulative-window): sorted imports * chore(cumulative-window): sorted imports * chore(cumulative-window): sorted imports * chore(cumulative-window): removed error from next window for * chore(cumulative-window): removed error from next window for * chore(cumulative-window): added case for timezone * chore(cumulative-window): added validation for eval window * chore(cumulative-window): updated api structure for cumulative window * chore(cumulative-window): updated schedule enum --------- Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> Co-authored-by: Srikanth Chekuri --- ee/query-service/rules/anomaly.go | 13 +- ee/query-service/rules/manager.go | 12 +- pkg/query-service/rules/base_rule.go | 14 +- pkg/query-service/rules/manager.go | 17 +- pkg/query-service/rules/prom_rule.go | 3 +- pkg/query-service/rules/promrule_test.go | 12 +- .../rules/threshold_rule_test.go | 168 ++-- pkg/types/ruletypes/api_params.go | 5 + pkg/types/ruletypes/evaluation.go | 287 ++++++ pkg/types/ruletypes/evaluation_test.go | 878 ++++++++++++++++++ 10 files changed, 1311 insertions(+), 98 deletions(-) create mode 100644 pkg/types/ruletypes/evaluation.go create mode 100644 pkg/types/ruletypes/evaluation_test.go diff --git a/ee/query-service/rules/anomaly.go b/ee/query-service/rules/anomaly.go index ff0aa40be8d8..2ac3b56cb949 100644 --- a/ee/query-service/rules/anomaly.go +++ b/ee/query-service/rules/anomaly.go @@ -166,16 +166,9 @@ func (r *AnomalyRule) prepareQueryRange(ctx context.Context, ts time.Time) (*v3. ctx, "prepare query range request v4", "ts", ts.UnixMilli(), "eval_window", r.EvalWindow().Milliseconds(), "eval_delay", r.EvalDelay().Milliseconds(), ) - start := ts.Add(-time.Duration(r.EvalWindow())).UnixMilli() - end := ts.UnixMilli() - - if r.EvalDelay() > 0 { - start = start - int64(r.EvalDelay().Milliseconds()) - end = end - int64(r.EvalDelay().Milliseconds()) - } - // round to minute otherwise we could potentially miss data - start = start - (start % (60 * 1000)) - end = end - (end % (60 * 1000)) + st, en := r.Timestamps(ts) + start := st.UnixMilli() + end := en.UnixMilli() compositeQuery := r.Condition().CompositeQuery diff --git a/ee/query-service/rules/manager.go b/ee/query-service/rules/manager.go index bf5cbbbec117..3212031f9f3f 100644 --- a/ee/query-service/rules/manager.go +++ b/ee/query-service/rules/manager.go @@ -3,8 +3,10 @@ package rules import ( "context" "fmt" + "time" + "github.com/SigNoz/signoz/pkg/errors" basemodel "github.com/SigNoz/signoz/pkg/query-service/model" baserules "github.com/SigNoz/signoz/pkg/query-service/rules" "github.com/SigNoz/signoz/pkg/query-service/utils/labels" @@ -20,6 +22,10 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error) var task baserules.Task ruleId := baserules.RuleIdFromTaskName(opts.TaskName) + evaluation, err := opts.Rule.Evaluation.GetEvaluation() + if err != nil { + return nil, errors.NewInvalidInputf(errors.CodeInvalidInput, "evaluation is invalid: %v", err) + } if opts.Rule.RuleType == ruletypes.RuleTypeThreshold { // create a threshold rule tr, err := baserules.NewThresholdRule( @@ -40,7 +46,7 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error) rules = append(rules, tr) // create ch rule task for evalution - task = newTask(baserules.TaskTypeCh, opts.TaskName, time.Duration(opts.Rule.Frequency), rules, opts.ManagerOpts, opts.NotifyFunc, opts.MaintenanceStore, opts.OrgID) + task = newTask(baserules.TaskTypeCh, opts.TaskName, time.Duration(evaluation.GetFrequency()), rules, opts.ManagerOpts, opts.NotifyFunc, opts.MaintenanceStore, opts.OrgID) } else if opts.Rule.RuleType == ruletypes.RuleTypeProm { @@ -62,7 +68,7 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error) rules = append(rules, pr) // create promql rule task for evalution - task = newTask(baserules.TaskTypeProm, opts.TaskName, time.Duration(opts.Rule.Frequency), rules, opts.ManagerOpts, opts.NotifyFunc, opts.MaintenanceStore, opts.OrgID) + task = newTask(baserules.TaskTypeProm, opts.TaskName, time.Duration(evaluation.GetFrequency()), rules, opts.ManagerOpts, opts.NotifyFunc, opts.MaintenanceStore, opts.OrgID) } else if opts.Rule.RuleType == ruletypes.RuleTypeAnomaly { // create anomaly rule @@ -84,7 +90,7 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error) rules = append(rules, ar) // create anomaly rule task for evalution - task = newTask(baserules.TaskTypeCh, opts.TaskName, time.Duration(opts.Rule.Frequency), rules, opts.ManagerOpts, opts.NotifyFunc, opts.MaintenanceStore, opts.OrgID) + task = newTask(baserules.TaskTypeCh, opts.TaskName, time.Duration(evaluation.GetFrequency()), rules, opts.ManagerOpts, opts.NotifyFunc, opts.MaintenanceStore, opts.OrgID) } else { return nil, fmt.Errorf("unsupported rule type %s. Supported types: %s, %s", opts.Rule.RuleType, ruletypes.RuleTypeProm, ruletypes.RuleTypeThreshold) diff --git a/pkg/query-service/rules/base_rule.go b/pkg/query-service/rules/base_rule.go index be14b9133f9b..a0ddcbf8444d 100644 --- a/pkg/query-service/rules/base_rule.go +++ b/pkg/query-service/rules/base_rule.go @@ -9,6 +9,7 @@ import ( "sync" "time" + "github.com/SigNoz/signoz/pkg/errors" "github.com/SigNoz/signoz/pkg/query-service/converter" "github.com/SigNoz/signoz/pkg/query-service/interfaces" "github.com/SigNoz/signoz/pkg/query-service/model" @@ -87,6 +88,8 @@ type BaseRule struct { TemporalityMap map[string]map[v3.Temporality]bool sqlstore sqlstore.SQLStore + + evaluation ruletypes.Evaluation } type RuleOption func(*BaseRule) @@ -129,6 +132,10 @@ func NewBaseRule(id string, orgID valuer.UUID, p *ruletypes.PostableRule, reader if err != nil { return nil, err } + evaluation, err := p.Evaluation.GetEvaluation() + if err != nil { + return nil, errors.NewInvalidInputf(errors.CodeInvalidInput, "failed to get evaluation: %v", err) + } baseRule := &BaseRule{ id: id, @@ -146,6 +153,7 @@ func NewBaseRule(id string, orgID valuer.UUID, p *ruletypes.PostableRule, reader reader: reader, TemporalityMap: make(map[string]map[v3.Temporality]bool), Threshold: threshold, + evaluation: evaluation, } if baseRule.evalWindow == 0 { @@ -248,8 +256,10 @@ func (r *BaseRule) Unit() string { } func (r *BaseRule) Timestamps(ts time.Time) (time.Time, time.Time) { - start := ts.Add(-time.Duration(r.evalWindow)).UnixMilli() - end := ts.UnixMilli() + + st, en := r.evaluation.NextWindowFor(ts) + start := st.UnixMilli() + end := en.UnixMilli() if r.evalDelay > 0 { start = start - int64(r.evalDelay.Milliseconds()) diff --git a/pkg/query-service/rules/manager.go b/pkg/query-service/rules/manager.go index f80686682687..5264b28f85ff 100644 --- a/pkg/query-service/rules/manager.go +++ b/pkg/query-service/rules/manager.go @@ -12,12 +12,11 @@ import ( "go.uber.org/zap" - "errors" - "github.com/go-openapi/strfmt" "github.com/SigNoz/signoz/pkg/alertmanager" "github.com/SigNoz/signoz/pkg/cache" + "github.com/SigNoz/signoz/pkg/errors" "github.com/SigNoz/signoz/pkg/modules/organization" "github.com/SigNoz/signoz/pkg/prometheus" querierV5 "github.com/SigNoz/signoz/pkg/querier" @@ -147,6 +146,12 @@ func defaultPrepareTaskFunc(opts PrepareTaskOptions) (Task, error) { var task Task ruleId := RuleIdFromTaskName(opts.TaskName) + + evaluation, err := opts.Rule.Evaluation.GetEvaluation() + if err != nil { + return nil, errors.NewInvalidInputf(errors.CodeInvalidInput, "evaluation is invalid: %v", err) + } + if opts.Rule.RuleType == ruletypes.RuleTypeThreshold { // create a threshold rule tr, err := NewThresholdRule( @@ -167,7 +172,7 @@ func defaultPrepareTaskFunc(opts PrepareTaskOptions) (Task, error) { rules = append(rules, tr) // create ch rule task for evalution - task = newTask(TaskTypeCh, opts.TaskName, taskNamesuffix, time.Duration(opts.Rule.Frequency), rules, opts.ManagerOpts, opts.NotifyFunc, opts.MaintenanceStore, opts.OrgID) + task = newTask(TaskTypeCh, opts.TaskName, taskNamesuffix, time.Duration(evaluation.GetFrequency()), rules, opts.ManagerOpts, opts.NotifyFunc, opts.MaintenanceStore, opts.OrgID) } else if opts.Rule.RuleType == ruletypes.RuleTypeProm { @@ -189,7 +194,7 @@ func defaultPrepareTaskFunc(opts PrepareTaskOptions) (Task, error) { rules = append(rules, pr) // create promql rule task for evalution - task = newTask(TaskTypeProm, opts.TaskName, taskNamesuffix, time.Duration(opts.Rule.Frequency), rules, opts.ManagerOpts, opts.NotifyFunc, opts.MaintenanceStore, opts.OrgID) + task = newTask(TaskTypeProm, opts.TaskName, taskNamesuffix, time.Duration(evaluation.GetFrequency()), rules, opts.ManagerOpts, opts.NotifyFunc, opts.MaintenanceStore, opts.OrgID) } else { return nil, fmt.Errorf("unsupported rule type %s. Supported types: %s, %s", opts.Rule.RuleType, ruletypes.RuleTypeProm, ruletypes.RuleTypeThreshold) @@ -400,7 +405,7 @@ func (m *Manager) editTask(_ context.Context, orgID valuer.UUID, rule *ruletypes if err != nil { zap.L().Error("loading tasks failed", zap.Error(err)) - return errors.New("error preparing rule with given parameters, previous rule set restored") + return errors.NewInvalidInputf(errors.CodeInvalidInput, "error preparing rule with given parameters, previous rule set restored") } for _, r := range newTask.Rules() { @@ -593,7 +598,7 @@ func (m *Manager) addTask(_ context.Context, orgID valuer.UUID, rule *ruletypes. if err != nil { zap.L().Error("creating rule task failed", zap.String("name", taskName), zap.Error(err)) - return errors.New("error loading rules, previous rule set restored") + return errors.NewInvalidInputf(errors.CodeInvalidInput, "error loading rules, previous rule set restored") } for _, r := range newTask.Rules() { diff --git a/pkg/query-service/rules/prom_rule.go b/pkg/query-service/rules/prom_rule.go index ea07f85e04b7..773c86a2368b 100644 --- a/pkg/query-service/rules/prom_rule.go +++ b/pkg/query-service/rules/prom_rule.go @@ -123,8 +123,7 @@ func (r *PromRule) Eval(ctx context.Context, ts time.Time) (interface{}, error) prevState := r.State() - start := ts.Add(-r.evalWindow) - end := ts + start, end := r.Timestamps(ts) interval := 60 * time.Second // TODO(srikanthccv): this should be configurable valueFormatter := formatter.FromUnit(r.Unit()) diff --git a/pkg/query-service/rules/promrule_test.go b/pkg/query-service/rules/promrule_test.go index a4e0b94d06a9..17177de622c9 100644 --- a/pkg/query-service/rules/promrule_test.go +++ b/pkg/query-service/rules/promrule_test.go @@ -25,11 +25,13 @@ func getVectorValues(vectors []ruletypes.Sample) []float64 { func TestPromRuleShouldAlert(t *testing.T) { postableRule := ruletypes.PostableRule{ - AlertName: "Test Rule", - AlertType: ruletypes.AlertTypeMetric, - RuleType: ruletypes.RuleTypeProm, - EvalWindow: ruletypes.Duration(5 * time.Minute), - Frequency: ruletypes.Duration(1 * time.Minute), + AlertName: "Test Rule", + AlertType: ruletypes.AlertTypeMetric, + RuleType: ruletypes.RuleTypeProm, + Evaluation: &ruletypes.EvaluationEnvelope{Kind: ruletypes.RollingEvaluation, Spec: ruletypes.RollingWindow{ + EvalWindow: ruletypes.Duration(5 * time.Minute), + Frequency: ruletypes.Duration(1 * time.Minute), + }}, RuleCondition: &ruletypes.RuleCondition{ CompositeQuery: &v3.CompositeQuery{ QueryType: v3.QueryTypePromQL, diff --git a/pkg/query-service/rules/threshold_rule_test.go b/pkg/query-service/rules/threshold_rule_test.go index d6bc92c8ab44..d311a47e186e 100644 --- a/pkg/query-service/rules/threshold_rule_test.go +++ b/pkg/query-service/rules/threshold_rule_test.go @@ -31,11 +31,13 @@ import ( func TestThresholdRuleShouldAlert(t *testing.T) { postableRule := ruletypes.PostableRule{ - AlertName: "Tricky Condition Tests", - AlertType: ruletypes.AlertTypeMetric, - RuleType: ruletypes.RuleTypeThreshold, - EvalWindow: ruletypes.Duration(5 * time.Minute), - Frequency: ruletypes.Duration(1 * time.Minute), + AlertName: "Tricky Condition Tests", + AlertType: ruletypes.AlertTypeMetric, + RuleType: ruletypes.RuleTypeThreshold, + Evaluation: &ruletypes.EvaluationEnvelope{ruletypes.RollingEvaluation, ruletypes.RollingWindow{ + EvalWindow: ruletypes.Duration(5 * time.Minute), + Frequency: ruletypes.Duration(1 * time.Minute), + }}, RuleCondition: &ruletypes.RuleCondition{ CompositeQuery: &v3.CompositeQuery{ QueryType: v3.QueryTypeBuilder, @@ -886,11 +888,13 @@ func TestNormalizeLabelName(t *testing.T) { func TestPrepareLinksToLogs(t *testing.T) { postableRule := ruletypes.PostableRule{ - AlertName: "Tricky Condition Tests", - AlertType: ruletypes.AlertTypeLogs, - RuleType: ruletypes.RuleTypeThreshold, - EvalWindow: ruletypes.Duration(5 * time.Minute), - Frequency: ruletypes.Duration(1 * time.Minute), + AlertName: "Tricky Condition Tests", + AlertType: ruletypes.AlertTypeLogs, + RuleType: ruletypes.RuleTypeThreshold, + Evaluation: &ruletypes.EvaluationEnvelope{ruletypes.RollingEvaluation, ruletypes.RollingWindow{ + EvalWindow: ruletypes.Duration(5 * time.Minute), + Frequency: ruletypes.Duration(1 * time.Minute), + }}, RuleCondition: &ruletypes.RuleCondition{ CompositeQuery: &v3.CompositeQuery{ QueryType: v3.QueryTypeBuilder, @@ -938,11 +942,13 @@ func TestPrepareLinksToLogs(t *testing.T) { func TestPrepareLinksToLogsV5(t *testing.T) { postableRule := ruletypes.PostableRule{ - AlertName: "Tricky Condition Tests", - AlertType: ruletypes.AlertTypeLogs, - RuleType: ruletypes.RuleTypeThreshold, - EvalWindow: ruletypes.Duration(5 * time.Minute), - Frequency: ruletypes.Duration(1 * time.Minute), + AlertName: "Tricky Condition Tests", + AlertType: ruletypes.AlertTypeLogs, + RuleType: ruletypes.RuleTypeThreshold, + Evaluation: &ruletypes.EvaluationEnvelope{ruletypes.RollingEvaluation, ruletypes.RollingWindow{ + EvalWindow: ruletypes.Duration(5 * time.Minute), + Frequency: ruletypes.Duration(1 * time.Minute), + }}, RuleCondition: &ruletypes.RuleCondition{ CompositeQuery: &v3.CompositeQuery{ QueryType: v3.QueryTypeBuilder, @@ -997,11 +1003,13 @@ func TestPrepareLinksToLogsV5(t *testing.T) { func TestPrepareLinksToTracesV5(t *testing.T) { postableRule := ruletypes.PostableRule{ - AlertName: "Tricky Condition Tests", - AlertType: ruletypes.AlertTypeTraces, - RuleType: ruletypes.RuleTypeThreshold, - EvalWindow: ruletypes.Duration(5 * time.Minute), - Frequency: ruletypes.Duration(1 * time.Minute), + AlertName: "Tricky Condition Tests", + AlertType: ruletypes.AlertTypeTraces, + RuleType: ruletypes.RuleTypeThreshold, + Evaluation: &ruletypes.EvaluationEnvelope{ruletypes.RollingEvaluation, ruletypes.RollingWindow{ + EvalWindow: ruletypes.Duration(5 * time.Minute), + Frequency: ruletypes.Duration(1 * time.Minute), + }}, RuleCondition: &ruletypes.RuleCondition{ CompositeQuery: &v3.CompositeQuery{ QueryType: v3.QueryTypeBuilder, @@ -1056,11 +1064,13 @@ func TestPrepareLinksToTracesV5(t *testing.T) { func TestPrepareLinksToTraces(t *testing.T) { postableRule := ruletypes.PostableRule{ - AlertName: "Links to traces test", - AlertType: ruletypes.AlertTypeTraces, - RuleType: ruletypes.RuleTypeThreshold, - EvalWindow: ruletypes.Duration(5 * time.Minute), - Frequency: ruletypes.Duration(1 * time.Minute), + AlertName: "Links to traces test", + AlertType: ruletypes.AlertTypeTraces, + RuleType: ruletypes.RuleTypeThreshold, + Evaluation: &ruletypes.EvaluationEnvelope{ruletypes.RollingEvaluation, ruletypes.RollingWindow{ + EvalWindow: ruletypes.Duration(5 * time.Minute), + Frequency: ruletypes.Duration(1 * time.Minute), + }}, RuleCondition: &ruletypes.RuleCondition{ CompositeQuery: &v3.CompositeQuery{ QueryType: v3.QueryTypeBuilder, @@ -1108,11 +1118,13 @@ func TestPrepareLinksToTraces(t *testing.T) { func TestThresholdRuleLabelNormalization(t *testing.T) { postableRule := ruletypes.PostableRule{ - AlertName: "Tricky Condition Tests", - AlertType: ruletypes.AlertTypeMetric, - RuleType: ruletypes.RuleTypeThreshold, - EvalWindow: ruletypes.Duration(5 * time.Minute), - Frequency: ruletypes.Duration(1 * time.Minute), + AlertName: "Tricky Condition Tests", + AlertType: ruletypes.AlertTypeMetric, + RuleType: ruletypes.RuleTypeThreshold, + Evaluation: &ruletypes.EvaluationEnvelope{ruletypes.RollingEvaluation, ruletypes.RollingWindow{ + EvalWindow: ruletypes.Duration(5 * time.Minute), + Frequency: ruletypes.Duration(1 * time.Minute), + }}, RuleCondition: &ruletypes.RuleCondition{ CompositeQuery: &v3.CompositeQuery{ QueryType: v3.QueryTypeBuilder, @@ -1214,11 +1226,13 @@ func TestThresholdRuleLabelNormalization(t *testing.T) { func TestThresholdRuleEvalDelay(t *testing.T) { postableRule := ruletypes.PostableRule{ - AlertName: "Test Eval Delay", - AlertType: ruletypes.AlertTypeMetric, - RuleType: ruletypes.RuleTypeThreshold, - EvalWindow: ruletypes.Duration(5 * time.Minute), - Frequency: ruletypes.Duration(1 * time.Minute), + AlertName: "Test Eval Delay", + AlertType: ruletypes.AlertTypeMetric, + RuleType: ruletypes.RuleTypeThreshold, + Evaluation: &ruletypes.EvaluationEnvelope{ruletypes.RollingEvaluation, ruletypes.RollingWindow{ + EvalWindow: ruletypes.Duration(5 * time.Minute), + Frequency: ruletypes.Duration(1 * time.Minute), + }}, RuleCondition: &ruletypes.RuleCondition{ CompositeQuery: &v3.CompositeQuery{ QueryType: v3.QueryTypeClickHouseSQL, @@ -1275,11 +1289,13 @@ func TestThresholdRuleEvalDelay(t *testing.T) { func TestThresholdRuleClickHouseTmpl(t *testing.T) { postableRule := ruletypes.PostableRule{ - AlertName: "Tricky Condition Tests", - AlertType: ruletypes.AlertTypeMetric, - RuleType: ruletypes.RuleTypeThreshold, - EvalWindow: ruletypes.Duration(5 * time.Minute), - Frequency: ruletypes.Duration(1 * time.Minute), + AlertName: "Tricky Condition Tests", + AlertType: ruletypes.AlertTypeMetric, + RuleType: ruletypes.RuleTypeThreshold, + Evaluation: &ruletypes.EvaluationEnvelope{ruletypes.RollingEvaluation, ruletypes.RollingWindow{ + EvalWindow: ruletypes.Duration(5 * time.Minute), + Frequency: ruletypes.Duration(1 * time.Minute), + }}, RuleCondition: &ruletypes.RuleCondition{ CompositeQuery: &v3.CompositeQuery{ QueryType: v3.QueryTypeClickHouseSQL, @@ -1342,11 +1358,13 @@ func (m *queryMatcherAny) Match(x string, y string) error { func TestThresholdRuleUnitCombinations(t *testing.T) { postableRule := ruletypes.PostableRule{ - AlertName: "Units test", - AlertType: ruletypes.AlertTypeMetric, - RuleType: ruletypes.RuleTypeThreshold, - EvalWindow: ruletypes.Duration(5 * time.Minute), - Frequency: ruletypes.Duration(1 * time.Minute), + AlertName: "Units test", + AlertType: ruletypes.AlertTypeMetric, + RuleType: ruletypes.RuleTypeThreshold, + Evaluation: &ruletypes.EvaluationEnvelope{ruletypes.RollingEvaluation, ruletypes.RollingWindow{ + EvalWindow: ruletypes.Duration(5 * time.Minute), + Frequency: ruletypes.Duration(1 * time.Minute), + }}, RuleCondition: &ruletypes.RuleCondition{ CompositeQuery: &v3.CompositeQuery{ QueryType: v3.QueryTypeBuilder, @@ -1535,11 +1553,13 @@ func TestThresholdRuleUnitCombinations(t *testing.T) { func TestThresholdRuleNoData(t *testing.T) { postableRule := ruletypes.PostableRule{ - AlertName: "No data test", - AlertType: ruletypes.AlertTypeMetric, - RuleType: ruletypes.RuleTypeThreshold, - EvalWindow: ruletypes.Duration(5 * time.Minute), - Frequency: ruletypes.Duration(1 * time.Minute), + AlertName: "No data test", + AlertType: ruletypes.AlertTypeMetric, + RuleType: ruletypes.RuleTypeThreshold, + Evaluation: &ruletypes.EvaluationEnvelope{ruletypes.RollingEvaluation, ruletypes.RollingWindow{ + EvalWindow: ruletypes.Duration(5 * time.Minute), + Frequency: ruletypes.Duration(1 * time.Minute), + }}, RuleCondition: &ruletypes.RuleCondition{ CompositeQuery: &v3.CompositeQuery{ QueryType: v3.QueryTypeBuilder, @@ -1638,11 +1658,13 @@ func TestThresholdRuleNoData(t *testing.T) { func TestThresholdRuleTracesLink(t *testing.T) { postableRule := ruletypes.PostableRule{ - AlertName: "Traces link test", - AlertType: ruletypes.AlertTypeTraces, - RuleType: ruletypes.RuleTypeThreshold, - EvalWindow: ruletypes.Duration(5 * time.Minute), - Frequency: ruletypes.Duration(1 * time.Minute), + AlertName: "Traces link test", + AlertType: ruletypes.AlertTypeTraces, + RuleType: ruletypes.RuleTypeThreshold, + Evaluation: &ruletypes.EvaluationEnvelope{ruletypes.RollingEvaluation, ruletypes.RollingWindow{ + EvalWindow: ruletypes.Duration(5 * time.Minute), + Frequency: ruletypes.Duration(1 * time.Minute), + }}, RuleCondition: &ruletypes.RuleCondition{ CompositeQuery: &v3.CompositeQuery{ QueryType: v3.QueryTypeBuilder, @@ -1763,11 +1785,13 @@ func TestThresholdRuleTracesLink(t *testing.T) { func TestThresholdRuleLogsLink(t *testing.T) { postableRule := ruletypes.PostableRule{ - AlertName: "Logs link test", - AlertType: ruletypes.AlertTypeLogs, - RuleType: ruletypes.RuleTypeThreshold, - EvalWindow: ruletypes.Duration(5 * time.Minute), - Frequency: ruletypes.Duration(1 * time.Minute), + AlertName: "Logs link test", + AlertType: ruletypes.AlertTypeLogs, + RuleType: ruletypes.RuleTypeThreshold, + Evaluation: &ruletypes.EvaluationEnvelope{ruletypes.RollingEvaluation, ruletypes.RollingWindow{ + EvalWindow: ruletypes.Duration(5 * time.Minute), + Frequency: ruletypes.Duration(1 * time.Minute), + }}, RuleCondition: &ruletypes.RuleCondition{ CompositeQuery: &v3.CompositeQuery{ QueryType: v3.QueryTypeBuilder, @@ -1901,11 +1925,13 @@ func TestThresholdRuleLogsLink(t *testing.T) { func TestThresholdRuleShiftBy(t *testing.T) { target := float64(10) postableRule := ruletypes.PostableRule{ - AlertName: "Logs link test", - AlertType: ruletypes.AlertTypeLogs, - RuleType: ruletypes.RuleTypeThreshold, - EvalWindow: ruletypes.Duration(5 * time.Minute), - Frequency: ruletypes.Duration(1 * time.Minute), + AlertName: "Logs link test", + AlertType: ruletypes.AlertTypeLogs, + RuleType: ruletypes.RuleTypeThreshold, + Evaluation: &ruletypes.EvaluationEnvelope{ruletypes.RollingEvaluation, ruletypes.RollingWindow{ + EvalWindow: ruletypes.Duration(5 * time.Minute), + Frequency: ruletypes.Duration(1 * time.Minute), + }}, RuleCondition: &ruletypes.RuleCondition{ Thresholds: &ruletypes.RuleThresholdData{ Kind: ruletypes.BasicThresholdKind, @@ -1973,11 +1999,13 @@ func TestThresholdRuleShiftBy(t *testing.T) { func TestMultipleThresholdRule(t *testing.T) { postableRule := ruletypes.PostableRule{ - AlertName: "Mulitple threshold test", - AlertType: ruletypes.AlertTypeMetric, - RuleType: ruletypes.RuleTypeThreshold, - EvalWindow: ruletypes.Duration(5 * time.Minute), - Frequency: ruletypes.Duration(1 * time.Minute), + AlertName: "Mulitple threshold test", + AlertType: ruletypes.AlertTypeMetric, + RuleType: ruletypes.RuleTypeThreshold, + Evaluation: &ruletypes.EvaluationEnvelope{ruletypes.RollingEvaluation, ruletypes.RollingWindow{ + EvalWindow: ruletypes.Duration(5 * time.Minute), + Frequency: ruletypes.Duration(1 * time.Minute), + }}, RuleCondition: &ruletypes.RuleCondition{ CompositeQuery: &v3.CompositeQuery{ QueryType: v3.QueryTypeBuilder, diff --git a/pkg/types/ruletypes/api_params.go b/pkg/types/ruletypes/api_params.go index f4ad6b55cd2b..9285b070fbe6 100644 --- a/pkg/types/ruletypes/api_params.go +++ b/pkg/types/ruletypes/api_params.go @@ -50,6 +50,8 @@ type PostableRule struct { PreferredChannels []string `json:"preferredChannels,omitempty"` Version string `json:"version,omitempty"` + + Evaluation *EvaluationEnvelope `yaml:"evaluation,omitempty" json:"evaluation,omitempty"` } func (r *PostableRule) processRuleDefaults() error { @@ -98,6 +100,9 @@ func (r *PostableRule) processRuleDefaults() error { r.RuleCondition.Thresholds = &thresholdData } } + if r.Evaluation == nil { + r.Evaluation = &EvaluationEnvelope{RollingEvaluation, RollingWindow{EvalWindow: r.EvalWindow, Frequency: r.Frequency}} + } return r.Validate() } diff --git a/pkg/types/ruletypes/evaluation.go b/pkg/types/ruletypes/evaluation.go new file mode 100644 index 000000000000..7677bbbbcbab --- /dev/null +++ b/pkg/types/ruletypes/evaluation.go @@ -0,0 +1,287 @@ +package ruletypes + +import ( + "encoding/json" + "time" + + "github.com/SigNoz/signoz/pkg/errors" + "github.com/SigNoz/signoz/pkg/valuer" +) + +type EvaluationKind struct { + valuer.String +} + +var ( + RollingEvaluation = EvaluationKind{valuer.NewString("rolling")} + CumulativeEvaluation = EvaluationKind{valuer.NewString("cumulative")} +) + +type Evaluation interface { + NextWindowFor(curr time.Time) (time.Time, time.Time) + GetFrequency() Duration +} + +type RollingWindow struct { + EvalWindow Duration `json:"evalWindow"` + Frequency Duration `json:"frequency"` +} + +func (rollingWindow RollingWindow) Validate() error { + if rollingWindow.EvalWindow <= 0 { + return errors.NewInvalidInputf(errors.CodeInvalidInput, "evalWindow must be greater than zero") + } + if rollingWindow.Frequency <= 0 { + return errors.NewInvalidInputf(errors.CodeInvalidInput, "frequency must be greater than zero") + } + return nil +} + +func (rollingWindow RollingWindow) NextWindowFor(curr time.Time) (time.Time, time.Time) { + return curr.Add(time.Duration(-rollingWindow.EvalWindow)), curr +} + +func (rollingWindow RollingWindow) GetFrequency() Duration { + return rollingWindow.Frequency +} + +type CumulativeWindow struct { + Schedule CumulativeSchedule `json:"schedule"` + Frequency Duration `json:"frequency"` + Timezone string `json:"timezone"` +} + +type CumulativeSchedule struct { + Type ScheduleType `json:"type"` + Minute *int `json:"minute,omitempty"` // 0-59, for all types + Hour *int `json:"hour,omitempty"` // 0-23, for daily/weekly/monthly + Day *int `json:"day,omitempty"` // 1-31, for monthly + Weekday *int `json:"weekday,omitempty"` // 0-6 (Sunday=0), for weekly +} + +type ScheduleType struct { + valuer.String +} + +var ( + ScheduleTypeHourly = ScheduleType{valuer.NewString("hourly")} + ScheduleTypeDaily = ScheduleType{valuer.NewString("daily")} + ScheduleTypeWeekly = ScheduleType{valuer.NewString("weekly")} + ScheduleTypeMonthly = ScheduleType{valuer.NewString("monthly")} +) + +func (cumulativeWindow CumulativeWindow) Validate() error { + // Validate schedule + if err := cumulativeWindow.Schedule.Validate(); err != nil { + return err + } + + if _, err := time.LoadLocation(cumulativeWindow.Timezone); err != nil { + return errors.NewInvalidInputf(errors.CodeInvalidInput, "timezone is invalid") + } + if cumulativeWindow.Frequency <= 0 { + return errors.NewInvalidInputf(errors.CodeInvalidInput, "frequency must be greater than zero") + } + return nil +} + +func (cs CumulativeSchedule) Validate() error { + switch cs.Type { + case ScheduleTypeHourly: + if cs.Minute == nil { + return errors.NewInvalidInputf(errors.CodeInvalidInput, "minute must be specified for hourly schedule") + } + if *cs.Minute < 0 || *cs.Minute > 59 { + return errors.NewInvalidInputf(errors.CodeInvalidInput, "minute must be between 0 and 59") + } + case ScheduleTypeDaily: + if cs.Hour == nil || cs.Minute == nil { + return errors.NewInvalidInputf(errors.CodeInvalidInput, "hour and minute must be specified for daily schedule") + } + if *cs.Hour < 0 || *cs.Hour > 23 { + return errors.NewInvalidInputf(errors.CodeInvalidInput, "hour must be between 0 and 23") + } + if *cs.Minute < 0 || *cs.Minute > 59 { + return errors.NewInvalidInputf(errors.CodeInvalidInput, "minute must be between 0 and 59") + } + case ScheduleTypeWeekly: + if cs.Weekday == nil || cs.Hour == nil || cs.Minute == nil { + return errors.NewInvalidInputf(errors.CodeInvalidInput, "weekday, hour and minute must be specified for weekly schedule") + } + if *cs.Weekday < 0 || *cs.Weekday > 6 { + return errors.NewInvalidInputf(errors.CodeInvalidInput, "weekday must be between 0 and 6 (Sunday=0)") + } + if *cs.Hour < 0 || *cs.Hour > 23 { + return errors.NewInvalidInputf(errors.CodeInvalidInput, "hour must be between 0 and 23") + } + if *cs.Minute < 0 || *cs.Minute > 59 { + return errors.NewInvalidInputf(errors.CodeInvalidInput, "minute must be between 0 and 59") + } + case ScheduleTypeMonthly: + if cs.Day == nil || cs.Hour == nil || cs.Minute == nil { + return errors.NewInvalidInputf(errors.CodeInvalidInput, "day, hour and minute must be specified for monthly schedule") + } + if *cs.Day < 1 || *cs.Day > 31 { + return errors.NewInvalidInputf(errors.CodeInvalidInput, "day must be between 1 and 31") + } + if *cs.Hour < 0 || *cs.Hour > 23 { + return errors.NewInvalidInputf(errors.CodeInvalidInput, "hour must be between 0 and 23") + } + if *cs.Minute < 0 || *cs.Minute > 59 { + return errors.NewInvalidInputf(errors.CodeInvalidInput, "minute must be between 0 and 59") + } + default: + return errors.NewInvalidInputf(errors.CodeInvalidInput, "invalid schedule type") + } + return nil +} + +func (cumulativeWindow CumulativeWindow) NextWindowFor(curr time.Time) (time.Time, time.Time) { + loc := time.UTC + if cumulativeWindow.Timezone != "" { + if tz, err := time.LoadLocation(cumulativeWindow.Timezone); err == nil { + loc = tz + } + } + + currInTZ := curr.In(loc) + windowStart := cumulativeWindow.getLastScheduleTime(currInTZ, loc) + + return windowStart.In(time.UTC), currInTZ.In(time.UTC) +} + +func (cw CumulativeWindow) getLastScheduleTime(curr time.Time, loc *time.Location) time.Time { + schedule := cw.Schedule + + switch schedule.Type { + case ScheduleTypeHourly: + // Find the most recent hour boundary with the specified minute + minute := *schedule.Minute + candidate := time.Date(curr.Year(), curr.Month(), curr.Day(), curr.Hour(), minute, 0, 0, loc) + if candidate.After(curr) { + candidate = candidate.Add(-time.Hour) + } + return candidate + + case ScheduleTypeDaily: + // Find the most recent day boundary with the specified hour and minute + hour := *schedule.Hour + minute := *schedule.Minute + candidate := time.Date(curr.Year(), curr.Month(), curr.Day(), hour, minute, 0, 0, loc) + if candidate.After(curr) { + candidate = candidate.AddDate(0, 0, -1) + } + return candidate + + case ScheduleTypeWeekly: + weekday := time.Weekday(*schedule.Weekday) + hour := *schedule.Hour + minute := *schedule.Minute + + // Calculate days to subtract to reach the target weekday + daysBack := int(curr.Weekday() - weekday) + if daysBack < 0 { + daysBack += 7 + } + + candidate := time.Date(curr.Year(), curr.Month(), curr.Day(), hour, minute, 0, 0, loc).AddDate(0, 0, -daysBack) + if candidate.After(curr) { + candidate = candidate.AddDate(0, 0, -7) + } + return candidate + + case ScheduleTypeMonthly: + // Find the most recent month boundary with the specified day, hour and minute + targetDay := *schedule.Day + hour := *schedule.Hour + minute := *schedule.Minute + + // Try current month first + lastDayOfCurrentMonth := time.Date(curr.Year(), curr.Month()+1, 0, 0, 0, 0, 0, loc).Day() + dayInCurrentMonth := targetDay + if targetDay > lastDayOfCurrentMonth { + dayInCurrentMonth = lastDayOfCurrentMonth + } + + candidate := time.Date(curr.Year(), curr.Month(), dayInCurrentMonth, hour, minute, 0, 0, loc) + if candidate.After(curr) { + prevMonth := curr.AddDate(0, -1, 0) + lastDayOfPrevMonth := time.Date(prevMonth.Year(), prevMonth.Month()+1, 0, 0, 0, 0, 0, loc).Day() + dayInPrevMonth := targetDay + if targetDay > lastDayOfPrevMonth { + dayInPrevMonth = lastDayOfPrevMonth + } + candidate = time.Date(prevMonth.Year(), prevMonth.Month(), dayInPrevMonth, hour, minute, 0, 0, loc) + } + return candidate + + default: + return curr + } +} + +func (cumulativeWindow CumulativeWindow) GetFrequency() Duration { + return cumulativeWindow.Frequency +} + +type EvaluationEnvelope struct { + Kind EvaluationKind `json:"kind"` + Spec any `json:"spec"` +} + +func (e *EvaluationEnvelope) UnmarshalJSON(data []byte) error { + var raw map[string]json.RawMessage + if err := json.Unmarshal(data, &raw); err != nil { + return errors.NewInvalidInputf(errors.CodeInvalidInput, "failed to unmarshal evaluation: %v", err) + } + if err := json.Unmarshal(raw["kind"], &e.Kind); err != nil { + return errors.NewInvalidInputf(errors.CodeInvalidInput, "failed to unmarshal evaluation kind: %v", err) + } + switch e.Kind { + case RollingEvaluation: + var rollingWindow RollingWindow + if err := json.Unmarshal(raw["spec"], &rollingWindow); err != nil { + return errors.NewInvalidInputf(errors.CodeInvalidInput, "failed to unmarshal rolling window: %v", err) + } + err := rollingWindow.Validate() + if err != nil { + return errors.NewInvalidInputf(errors.CodeInvalidInput, "failed to validate rolling window: %v", err) + } + e.Spec = rollingWindow + case CumulativeEvaluation: + var cumulativeWindow CumulativeWindow + if err := json.Unmarshal(raw["spec"], &cumulativeWindow); err != nil { + return errors.NewInvalidInputf(errors.CodeInvalidInput, "failed to unmarshal cumulative window: %v", err) + } + err := cumulativeWindow.Validate() + if err != nil { + return errors.NewInvalidInputf(errors.CodeInvalidInput, "failed to validate cumulative window: %v", err) + } + e.Spec = cumulativeWindow + + default: + return errors.NewInvalidInputf(errors.CodeUnsupported, "unknown evaluation kind") + } + + return nil +} + +func (e *EvaluationEnvelope) GetEvaluation() (Evaluation, error) { + if e.Kind.IsZero() { + e.Kind = RollingEvaluation + } + + switch e.Kind { + case RollingEvaluation: + if rolling, ok := e.Spec.(RollingWindow); ok { + return rolling, nil + } + case CumulativeEvaluation: + if cumulative, ok := e.Spec.(CumulativeWindow); ok { + return cumulative, nil + } + default: + return nil, errors.NewInvalidInputf(errors.CodeUnsupported, "unknown evaluation kind") + } + return nil, errors.NewInvalidInputf(errors.CodeUnsupported, "unknown evaluation kind") +} diff --git a/pkg/types/ruletypes/evaluation_test.go b/pkg/types/ruletypes/evaluation_test.go new file mode 100644 index 000000000000..aded4c10e04a --- /dev/null +++ b/pkg/types/ruletypes/evaluation_test.go @@ -0,0 +1,878 @@ +package ruletypes + +import ( + "encoding/json" + "testing" + "time" +) + +func TestRollingWindow_EvaluationTime(t *testing.T) { + tests := []struct { + name string + evalWindow Duration + current time.Time + wantStart time.Time + wantEnd time.Time + }{ + { + name: "5 minute rolling window", + evalWindow: Duration(5 * time.Minute), + current: time.Date(2023, 12, 1, 12, 30, 0, 0, time.UTC), + wantStart: time.Date(2023, 12, 1, 12, 25, 0, 0, time.UTC), + wantEnd: time.Date(2023, 12, 1, 12, 30, 0, 0, time.UTC), + }, + { + name: "1 hour rolling window", + evalWindow: Duration(1 * time.Hour), + current: time.Date(2023, 12, 1, 15, 45, 30, 0, time.UTC), + wantStart: time.Date(2023, 12, 1, 14, 45, 30, 0, time.UTC), + wantEnd: time.Date(2023, 12, 1, 15, 45, 30, 0, time.UTC), + }, + { + name: "30 second rolling window", + evalWindow: Duration(30 * time.Second), + current: time.Date(2023, 12, 1, 12, 30, 15, 0, time.UTC), + wantStart: time.Date(2023, 12, 1, 12, 29, 45, 0, time.UTC), + wantEnd: time.Date(2023, 12, 1, 12, 30, 15, 0, time.UTC), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rw := &RollingWindow{ + EvalWindow: tt.evalWindow, + Frequency: Duration(1 * time.Minute), + } + + gotStart, gotEnd := rw.NextWindowFor(tt.current) + if !gotStart.Equal(tt.wantStart) { + t.Errorf("RollingWindow.NextWindowFor() start time = %v, want %v", gotStart, tt.wantStart) + } + if !gotEnd.Equal(tt.wantEnd) { + t.Errorf("RollingWindow.NextWindowFor() end time = %v, want %v", gotEnd, tt.wantEnd) + } + }) + } +} + +func TestCumulativeWindow_NewScheduleSystem(t *testing.T) { + tests := []struct { + name string + window CumulativeWindow + current time.Time + wantErr bool + }{ + { + name: "hourly schedule - minute 15", + window: CumulativeWindow{ + Schedule: CumulativeSchedule{ + Type: ScheduleTypeHourly, + Minute: intPtr(15), + }, + Frequency: Duration(5 * time.Minute), + Timezone: "UTC", + }, + current: time.Date(2025, 3, 15, 14, 30, 0, 0, time.UTC), + wantErr: false, + }, + { + name: "daily schedule - 9:30 AM IST", + window: CumulativeWindow{ + Schedule: CumulativeSchedule{ + Type: ScheduleTypeDaily, + Hour: intPtr(9), + Minute: intPtr(30), + }, + Frequency: Duration(1 * time.Hour), + Timezone: "Asia/Kolkata", + }, + current: time.Date(2025, 3, 15, 15, 30, 0, 0, time.UTC), + wantErr: false, + }, + { + name: "weekly schedule - Monday 2:00 PM", + window: CumulativeWindow{ + Schedule: CumulativeSchedule{ + Type: ScheduleTypeWeekly, + Weekday: intPtr(1), // Monday + Hour: intPtr(14), + Minute: intPtr(0), + }, + Frequency: Duration(24 * time.Hour), + Timezone: "America/New_York", + }, + current: time.Date(2025, 3, 18, 19, 0, 0, 0, time.UTC), // Tuesday + wantErr: false, + }, + { + name: "monthly schedule - 1st at midnight", + window: CumulativeWindow{ + Schedule: CumulativeSchedule{ + Type: ScheduleTypeMonthly, + Day: intPtr(1), + Hour: intPtr(0), + Minute: intPtr(0), + }, + Frequency: Duration(24 * time.Hour), + Timezone: "UTC", + }, + current: time.Date(2025, 3, 15, 12, 0, 0, 0, time.UTC), + wantErr: false, + }, + { + name: "invalid schedule - missing minute for hourly", + window: CumulativeWindow{ + Schedule: CumulativeSchedule{ + Type: ScheduleTypeHourly, + }, + Frequency: Duration(5 * time.Minute), + Timezone: "UTC", + }, + current: time.Date(2025, 3, 15, 14, 30, 0, 0, time.UTC), + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Test validation + err := tt.window.Validate() + if (err != nil) != tt.wantErr { + t.Errorf("CumulativeWindow.Validate() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if !tt.wantErr { + // Test NextWindowFor + start, end := tt.window.NextWindowFor(tt.current) + + // Basic validation + if start.After(end) { + t.Errorf("Window start should not be after end: start=%v, end=%v", start, end) + } + + if end.After(tt.current) { + t.Errorf("Window end should not be after current time: end=%v, current=%v", end, tt.current) + } + } + }) + } +} + +func intPtr(i int) *int { + return &i +} + +func TestCumulativeWindow_NextWindowFor(t *testing.T) { + tests := []struct { + name string + window CumulativeWindow + current time.Time + wantStart time.Time + wantEnd time.Time + }{ + // Hourly schedule tests + { + name: "hourly - current at exact minute", + window: CumulativeWindow{ + Schedule: CumulativeSchedule{ + Type: ScheduleTypeHourly, + Minute: intPtr(30), + }, + Timezone: "UTC", + }, + current: time.Date(2025, 3, 15, 14, 30, 0, 0, time.UTC), + wantStart: time.Date(2025, 3, 15, 14, 30, 0, 0, time.UTC), + wantEnd: time.Date(2025, 3, 15, 14, 30, 0, 0, time.UTC), + }, + { + name: "hourly - current after scheduled minute", + window: CumulativeWindow{ + Schedule: CumulativeSchedule{ + Type: ScheduleTypeHourly, + Minute: intPtr(15), + }, + Timezone: "UTC", + }, + current: time.Date(2025, 3, 15, 14, 45, 0, 0, time.UTC), + wantStart: time.Date(2025, 3, 15, 14, 15, 0, 0, time.UTC), + wantEnd: time.Date(2025, 3, 15, 14, 45, 0, 0, time.UTC), + }, + { + name: "hourly - current before scheduled minute", + window: CumulativeWindow{ + Schedule: CumulativeSchedule{ + Type: ScheduleTypeHourly, + Minute: intPtr(30), + }, + Timezone: "UTC", + }, + current: time.Date(2025, 3, 15, 14, 15, 0, 0, time.UTC), + wantStart: time.Date(2025, 3, 15, 13, 30, 0, 0, time.UTC), // Previous hour + wantEnd: time.Date(2025, 3, 15, 14, 15, 0, 0, time.UTC), + }, + { + name: "hourly - current before scheduled minute", + window: CumulativeWindow{ + Schedule: CumulativeSchedule{ + Type: ScheduleTypeHourly, + Minute: intPtr(30), + }, + Timezone: "UTC", + }, + current: time.Date(2025, 3, 15, 13, 14, 0, 0, time.UTC), + wantStart: time.Date(2025, 3, 15, 12, 30, 0, 0, time.UTC), // Previous hour + wantEnd: time.Date(2025, 3, 15, 13, 14, 0, 0, time.UTC), + }, + { + name: "hourly - current before scheduled minute", + window: CumulativeWindow{ + Schedule: CumulativeSchedule{ + Type: ScheduleTypeHourly, + Minute: intPtr(30), + }, + Timezone: "Asia/Kolkata", + }, + current: time.Date(2025, 3, 15, 13, 14, 0, 0, time.UTC), + wantStart: time.Date(2025, 3, 15, 13, 00, 0, 0, time.UTC), // Previous hour + wantEnd: time.Date(2025, 3, 15, 13, 14, 0, 0, time.UTC), + }, + + // Daily schedule tests + { + name: "daily - current at exact time", + window: CumulativeWindow{ + Schedule: CumulativeSchedule{ + Type: ScheduleTypeDaily, + Hour: intPtr(9), + Minute: intPtr(30), + }, + Timezone: "UTC", + }, + current: time.Date(2025, 3, 15, 9, 30, 0, 0, time.UTC), + wantStart: time.Date(2025, 3, 15, 9, 30, 0, 0, time.UTC), + wantEnd: time.Date(2025, 3, 15, 9, 30, 0, 0, time.UTC), + }, + { + name: "daily - current after scheduled time", + window: CumulativeWindow{ + Schedule: CumulativeSchedule{ + Type: ScheduleTypeDaily, + Hour: intPtr(9), + Minute: intPtr(30), + }, + Timezone: "UTC", + }, + current: time.Date(2025, 3, 15, 15, 45, 0, 0, time.UTC), + wantStart: time.Date(2025, 3, 15, 9, 30, 0, 0, time.UTC), + wantEnd: time.Date(2025, 3, 15, 15, 45, 0, 0, time.UTC), + }, + { + name: "daily - current before scheduled time", + window: CumulativeWindow{ + Schedule: CumulativeSchedule{ + Type: ScheduleTypeDaily, + Hour: intPtr(9), + Minute: intPtr(30), + }, + Timezone: "UTC", + }, + current: time.Date(2025, 3, 15, 8, 15, 0, 0, time.UTC), + wantStart: time.Date(2025, 3, 14, 9, 30, 0, 0, time.UTC), // Previous day + wantEnd: time.Date(2025, 3, 15, 8, 15, 0, 0, time.UTC), + }, + { + name: "daily - with timezone IST", + window: CumulativeWindow{ + Schedule: CumulativeSchedule{ + Type: ScheduleTypeDaily, + Hour: intPtr(9), + Minute: intPtr(30), + }, + Timezone: "Asia/Kolkata", + }, + current: time.Date(2025, 3, 15, 15, 30, 0, 0, time.UTC), // 9:00 PM IST + wantStart: time.Date(2025, 3, 15, 4, 0, 0, 0, time.UTC), // 9:30 AM IST in UTC + wantEnd: time.Date(2025, 3, 15, 15, 30, 0, 0, time.UTC), + }, + + // Weekly schedule tests + { + name: "weekly - current on scheduled day at exact time", + window: CumulativeWindow{ + Schedule: CumulativeSchedule{ + Type: ScheduleTypeWeekly, + Weekday: intPtr(1), // Monday + Hour: intPtr(14), + Minute: intPtr(0), + }, + Timezone: "UTC", + }, + current: time.Date(2025, 3, 17, 14, 0, 0, 0, time.UTC), // Monday + wantStart: time.Date(2025, 3, 17, 14, 0, 0, 0, time.UTC), + wantEnd: time.Date(2025, 3, 17, 14, 0, 0, 0, time.UTC), + }, + { + name: "weekly - current on different day", + window: CumulativeWindow{ + Schedule: CumulativeSchedule{ + Type: ScheduleTypeWeekly, + Weekday: intPtr(1), // Monday + Hour: intPtr(14), + Minute: intPtr(0), + }, + Timezone: "UTC", + }, + current: time.Date(2025, 3, 19, 10, 30, 0, 0, time.UTC), // Wednesday + wantStart: time.Date(2025, 3, 17, 14, 0, 0, 0, time.UTC), // Previous Monday + wantEnd: time.Date(2025, 3, 19, 10, 30, 0, 0, time.UTC), + }, + { + name: "weekly - current before scheduled time on same day", + window: CumulativeWindow{ + Schedule: CumulativeSchedule{ + Type: ScheduleTypeWeekly, + Weekday: intPtr(2), // Tuesday + Hour: intPtr(14), + Minute: intPtr(0), + }, + Timezone: "UTC", + }, + current: time.Date(2025, 3, 18, 10, 0, 0, 0, time.UTC), // Tuesday before 2 PM + wantStart: time.Date(2025, 3, 11, 14, 0, 0, 0, time.UTC), // Previous Tuesday + wantEnd: time.Date(2025, 3, 18, 10, 0, 0, 0, time.UTC), + }, + + // Monthly schedule tests + { + name: "monthly - current on scheduled day at exact time", + window: CumulativeWindow{ + Schedule: CumulativeSchedule{ + Type: ScheduleTypeMonthly, + Day: intPtr(15), + Hour: intPtr(12), + Minute: intPtr(0), + }, + Timezone: "UTC", + }, + current: time.Date(2025, 3, 15, 12, 0, 0, 0, time.UTC), + wantStart: time.Date(2025, 3, 15, 12, 0, 0, 0, time.UTC), + wantEnd: time.Date(2025, 3, 15, 12, 0, 0, 0, time.UTC), + }, + { + name: "monthly - current after scheduled time", + window: CumulativeWindow{ + Schedule: CumulativeSchedule{ + Type: ScheduleTypeMonthly, + Day: intPtr(1), + Hour: intPtr(0), + Minute: intPtr(0), + }, + Timezone: "UTC", + }, + current: time.Date(2025, 3, 15, 16, 30, 0, 0, time.UTC), + wantStart: time.Date(2025, 3, 1, 0, 0, 0, 0, time.UTC), + wantEnd: time.Date(2025, 3, 15, 16, 30, 0, 0, time.UTC), + }, + { + name: "monthly - current before scheduled day", + window: CumulativeWindow{ + Schedule: CumulativeSchedule{ + Type: ScheduleTypeMonthly, + Day: intPtr(15), + Hour: intPtr(12), + Minute: intPtr(0), + }, + Timezone: "UTC", + }, + current: time.Date(2025, 3, 10, 10, 0, 0, 0, time.UTC), + wantStart: time.Date(2025, 2, 15, 12, 0, 0, 0, time.UTC), // Previous month + wantEnd: time.Date(2025, 3, 10, 10, 0, 0, 0, time.UTC), + }, + { + name: "monthly - day 31 in february (edge case)", + window: CumulativeWindow{ + Schedule: CumulativeSchedule{ + Type: ScheduleTypeMonthly, + Day: intPtr(31), + Hour: intPtr(12), + Minute: intPtr(0), + }, + Timezone: "UTC", + }, + current: time.Date(2025, 3, 15, 10, 0, 0, 0, time.UTC), + wantStart: time.Date(2025, 2, 28, 12, 0, 0, 0, time.UTC), // Feb 28 (last day of Feb) + wantEnd: time.Date(2025, 3, 15, 10, 0, 0, 0, time.UTC), + }, + + // Comprehensive timezone-based test cases + { + name: "Asia/Tokyo timezone - hourly schedule", + window: CumulativeWindow{ + Schedule: CumulativeSchedule{ + Type: ScheduleTypeHourly, + Minute: intPtr(45), + }, + Timezone: "Asia/Tokyo", + }, + current: time.Date(2023, 12, 15, 2, 30, 0, 0, time.UTC), // 11:30 AM JST + wantStart: time.Date(2023, 12, 15, 1, 45, 0, 0, time.UTC), // 10:45 AM JST in UTC + wantEnd: time.Date(2023, 12, 15, 2, 30, 0, 0, time.UTC), + }, + { + name: "America/New_York timezone - daily schedule (EST)", + window: CumulativeWindow{ + Schedule: CumulativeSchedule{ + Type: ScheduleTypeDaily, + Hour: intPtr(8), // 8 AM EST + Minute: intPtr(0), + }, + Timezone: "America/New_York", + }, + current: time.Date(2023, 12, 15, 20, 30, 0, 0, time.UTC), // 3:30 PM EST + wantStart: time.Date(2023, 12, 15, 13, 0, 0, 0, time.UTC), // 8 AM EST in UTC + wantEnd: time.Date(2023, 12, 15, 20, 30, 0, 0, time.UTC), + }, + { + name: "Europe/London timezone - weekly schedule (GMT)", + window: CumulativeWindow{ + Schedule: CumulativeSchedule{ + Type: ScheduleTypeWeekly, + Weekday: intPtr(1), // Monday + Hour: intPtr(12), + Minute: intPtr(0), + }, + Timezone: "Europe/London", + }, + current: time.Date(2023, 12, 15, 15, 0, 0, 0, time.UTC), // Friday 3 PM GMT + wantStart: time.Date(2023, 12, 11, 12, 0, 0, 0, time.UTC), // Previous Monday 12 PM GMT + wantEnd: time.Date(2023, 12, 15, 15, 0, 0, 0, time.UTC), + }, + { + name: "Australia/Sydney timezone - monthly schedule (AEDT)", + window: CumulativeWindow{ + Schedule: CumulativeSchedule{ + Type: ScheduleTypeMonthly, + Day: intPtr(1), + Hour: intPtr(0), // Midnight AEDT + Minute: intPtr(0), + }, + Timezone: "Australia/Sydney", + }, + current: time.Date(2023, 12, 15, 5, 0, 0, 0, time.UTC), // 4 PM AEDT on 15th + wantStart: time.Date(2023, 11, 30, 13, 0, 0, 0, time.UTC), // Midnight AEDT on Dec 1st in UTC (Nov 30 13:00 UTC) + wantEnd: time.Date(2023, 12, 15, 5, 0, 0, 0, time.UTC), + }, + { + name: "Pacific/Honolulu timezone - hourly schedule (HST)", + window: CumulativeWindow{ + Schedule: CumulativeSchedule{ + Type: ScheduleTypeHourly, + Minute: intPtr(30), + }, + Timezone: "Pacific/Honolulu", + }, + current: time.Date(2023, 12, 15, 22, 45, 0, 0, time.UTC), // 12:45 PM HST + wantStart: time.Date(2023, 12, 15, 22, 30, 0, 0, time.UTC), // 12:30 PM HST in UTC + wantEnd: time.Date(2023, 12, 15, 22, 45, 0, 0, time.UTC), + }, + { + name: "America/Los_Angeles timezone - DST transition daily", + window: CumulativeWindow{ + Schedule: CumulativeSchedule{ + Type: ScheduleTypeDaily, + Hour: intPtr(2), // 2 AM PST/PDT + Minute: intPtr(0), + }, + Timezone: "America/Los_Angeles", + }, + current: time.Date(2023, 3, 12, 15, 0, 0, 0, time.UTC), // Day after DST starts + wantStart: time.Date(2023, 3, 12, 9, 0, 0, 0, time.UTC), // 2 AM PDT in UTC (PDT = UTC-7) + wantEnd: time.Date(2023, 3, 12, 15, 0, 0, 0, time.UTC), + }, + { + name: "Europe/Berlin timezone - weekly schedule (CET)", + window: CumulativeWindow{ + Schedule: CumulativeSchedule{ + Type: ScheduleTypeWeekly, + Weekday: intPtr(5), // Friday + Hour: intPtr(16), // 4 PM CET + Minute: intPtr(30), + }, + Timezone: "Europe/Berlin", + }, + current: time.Date(2023, 12, 18, 10, 0, 0, 0, time.UTC), // Monday 11 AM CET + wantStart: time.Date(2023, 12, 15, 15, 30, 0, 0, time.UTC), // Previous Friday 4:30 PM CET + wantEnd: time.Date(2023, 12, 18, 10, 0, 0, 0, time.UTC), + }, + { + name: "Asia/Kolkata timezone - monthly edge case (IST)", + window: CumulativeWindow{ + Schedule: CumulativeSchedule{ + Type: ScheduleTypeMonthly, + Day: intPtr(31), // 31st (edge case for Feb) + Hour: intPtr(23), + Minute: intPtr(59), + }, + Timezone: "Asia/Kolkata", + }, + current: time.Date(2023, 3, 10, 12, 0, 0, 0, time.UTC), // March 10th 5:30 PM IST + wantStart: time.Date(2023, 2, 28, 18, 29, 0, 0, time.UTC), // Feb 28 11:59 PM IST (last day of Feb) + wantEnd: time.Date(2023, 3, 10, 12, 0, 0, 0, time.UTC), + }, + { + name: "America/Chicago timezone - hourly across midnight (CST)", + window: CumulativeWindow{ + Schedule: CumulativeSchedule{ + Type: ScheduleTypeHourly, + Minute: intPtr(0), // Top of hour + }, + Timezone: "America/Chicago", + }, + current: time.Date(2023, 12, 15, 6, 30, 0, 0, time.UTC), // 12:30 AM CST + wantStart: time.Date(2023, 12, 15, 6, 0, 0, 0, time.UTC), // Midnight CST in UTC + wantEnd: time.Date(2023, 12, 15, 6, 30, 0, 0, time.UTC), + }, + + // Boundary condition test cases + { + name: "boundary - end of year transition (Dec 31 to Jan 1)", + window: CumulativeWindow{ + Schedule: CumulativeSchedule{ + Type: ScheduleTypeDaily, + Hour: intPtr(0), + Minute: intPtr(0), + }, + Timezone: "UTC", + }, + current: time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC), // Jan 1st noon + wantStart: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), // Jan 1st midnight + wantEnd: time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC), + }, + { + name: "boundary - leap year Feb 29th monthly schedule", + window: CumulativeWindow{ + Schedule: CumulativeSchedule{ + Type: ScheduleTypeMonthly, + Day: intPtr(29), + Hour: intPtr(15), + Minute: intPtr(30), + }, + Timezone: "UTC", + }, + current: time.Date(2024, 3, 10, 10, 0, 0, 0, time.UTC), // March 10th (leap year) + wantStart: time.Date(2024, 2, 29, 15, 30, 0, 0, time.UTC), // Feb 29th exists in leap year + wantEnd: time.Date(2024, 3, 10, 10, 0, 0, 0, time.UTC), + }, + { + name: "boundary - non-leap year Feb 29th request (fallback to Feb 28th)", + window: CumulativeWindow{ + Schedule: CumulativeSchedule{ + Type: ScheduleTypeMonthly, + Day: intPtr(29), + Hour: intPtr(15), + Minute: intPtr(30), + }, + Timezone: "UTC", + }, + current: time.Date(2023, 3, 10, 10, 0, 0, 0, time.UTC), // March 10th (non-leap year) + wantStart: time.Date(2023, 2, 28, 15, 30, 0, 0, time.UTC), // Feb 28th (fallback) + wantEnd: time.Date(2023, 3, 10, 10, 0, 0, 0, time.UTC), + }, + { + name: "boundary - day 31 in April (30-day month fallback)", + window: CumulativeWindow{ + Schedule: CumulativeSchedule{ + Type: ScheduleTypeMonthly, + Day: intPtr(31), + Hour: intPtr(12), + Minute: intPtr(0), + }, + Timezone: "UTC", + }, + current: time.Date(2023, 5, 15, 10, 0, 0, 0, time.UTC), // May 15th + wantStart: time.Date(2023, 4, 30, 12, 0, 0, 0, time.UTC), // April 30th (fallback from 31st) + wantEnd: time.Date(2023, 5, 15, 10, 0, 0, 0, time.UTC), + }, + { + name: "boundary - weekly Sunday to Monday transition", + window: CumulativeWindow{ + Schedule: CumulativeSchedule{ + Type: ScheduleTypeWeekly, + Weekday: intPtr(0), // Sunday + Hour: intPtr(23), + Minute: intPtr(59), + }, + Timezone: "UTC", + }, + current: time.Date(2023, 12, 11, 1, 0, 0, 0, time.UTC), // Monday 1 AM + wantStart: time.Date(2023, 12, 10, 23, 59, 0, 0, time.UTC), // Previous Sunday 11:59 PM + wantEnd: time.Date(2023, 12, 11, 1, 0, 0, 0, time.UTC), + }, + { + name: "boundary - hourly minute 59 to minute 0 transition", + window: CumulativeWindow{ + Schedule: CumulativeSchedule{ + Type: ScheduleTypeHourly, + Minute: intPtr(59), + }, + Timezone: "UTC", + }, + current: time.Date(2023, 12, 15, 14, 5, 0, 0, time.UTC), // 14:05 + wantStart: time.Date(2023, 12, 15, 13, 59, 0, 0, time.UTC), // 13:59 (previous hour) + wantEnd: time.Date(2023, 12, 15, 14, 5, 0, 0, time.UTC), + }, + { + name: "boundary - DST spring forward (2 AM doesn't exist)", + window: CumulativeWindow{ + Schedule: CumulativeSchedule{ + Type: ScheduleTypeDaily, + Hour: intPtr(2), // 2 AM (skipped during DST) + Minute: intPtr(30), + }, + Timezone: "America/New_York", + }, + current: time.Date(2023, 3, 12, 15, 0, 0, 0, time.UTC), // Day DST starts + wantStart: time.Date(2023, 3, 12, 6, 30, 0, 0, time.UTC), // Same day 2:30 AM EDT (adjusted for DST) + wantEnd: time.Date(2023, 3, 12, 15, 0, 0, 0, time.UTC), + }, + { + name: "boundary - DST fall back (2 AM occurs twice)", + window: CumulativeWindow{ + Schedule: CumulativeSchedule{ + Type: ScheduleTypeDaily, + Hour: intPtr(2), // 2 AM (occurs twice) + Minute: intPtr(30), + }, + Timezone: "America/New_York", + }, + current: time.Date(2023, 11, 5, 15, 0, 0, 0, time.UTC), // Day DST ends + wantStart: time.Date(2023, 11, 5, 7, 30, 0, 0, time.UTC), // Same day 2:30 AM EST (after fall back) + wantEnd: time.Date(2023, 11, 5, 15, 0, 0, 0, time.UTC), + }, + { + name: "boundary - month transition January to February", + window: CumulativeWindow{ + Schedule: CumulativeSchedule{ + Type: ScheduleTypeMonthly, + Day: intPtr(31), + Hour: intPtr(0), + Minute: intPtr(0), + }, + Timezone: "UTC", + }, + current: time.Date(2023, 2, 15, 12, 0, 0, 0, time.UTC), // February 15th + wantStart: time.Date(2023, 1, 31, 0, 0, 0, 0, time.UTC), // January 31st (exists) + wantEnd: time.Date(2023, 2, 15, 12, 0, 0, 0, time.UTC), + }, + { + name: "boundary - extreme timezone offset (+14 hours)", + window: CumulativeWindow{ + Schedule: CumulativeSchedule{ + Type: ScheduleTypeDaily, + Hour: intPtr(12), + Minute: intPtr(0), + }, + Timezone: "Pacific/Kiritimati", // UTC+14 + }, + current: time.Date(2023, 12, 15, 5, 0, 0, 0, time.UTC), // 7 PM local time + wantStart: time.Date(2023, 12, 14, 22, 0, 0, 0, time.UTC), // 12 PM local time (previous day in UTC) + wantEnd: time.Date(2023, 12, 15, 5, 0, 0, 0, time.UTC), + }, + { + name: "boundary - extreme timezone offset (-12 hours)", + window: CumulativeWindow{ + Schedule: CumulativeSchedule{ + Type: ScheduleTypeDaily, + Hour: intPtr(12), + Minute: intPtr(0), + }, + Timezone: "Etc/GMT+12", // UTC-12 (use standard timezone name) + }, + current: time.Date(2023, 12, 15, 5, 0, 0, 0, time.UTC), // 5 PM previous day local time + wantStart: time.Date(2023, 12, 15, 0, 0, 0, 0, time.UTC), // 12 PM local time (same day in UTC) + wantEnd: time.Date(2023, 12, 15, 5, 0, 0, 0, time.UTC), + }, + { + name: "boundary - week boundary Saturday to Sunday", + window: CumulativeWindow{ + Schedule: CumulativeSchedule{ + Type: ScheduleTypeWeekly, + Weekday: intPtr(6), // Saturday + Hour: intPtr(0), + Minute: intPtr(0), + }, + Timezone: "UTC", + }, + current: time.Date(2023, 12, 17, 12, 0, 0, 0, time.UTC), // Sunday noon + wantStart: time.Date(2023, 12, 16, 0, 0, 0, 0, time.UTC), // Saturday midnight + wantEnd: time.Date(2023, 12, 17, 12, 0, 0, 0, time.UTC), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotStart, gotEnd := tt.window.NextWindowFor(tt.current) + + if !gotStart.Equal(tt.wantStart) { + t.Errorf("NextWindowFor() start = %v, want %v", gotStart, tt.wantStart) + } + if !gotEnd.Equal(tt.wantEnd) { + t.Errorf("NextWindowFor() end = %v, want %v", gotEnd, tt.wantEnd) + } + + // Validate basic invariants + if gotStart.After(gotEnd) { + t.Errorf("Window start should not be after end: start=%v, end=%v", gotStart, gotEnd) + } + if gotEnd.After(tt.current) { + t.Errorf("Window end should not be after current time: end=%v, current=%v", gotEnd, tt.current) + } + + duration := gotEnd.Sub(gotStart) + + // Validate window length is reasonable + if duration < 0 { + t.Errorf("Window duration should not be negative: %v", duration) + } + if duration > 366*24*time.Hour { + t.Errorf("Window duration should not exceed 1 year: %v", duration) + } + }) + } +} + +func TestEvaluationEnvelope_UnmarshalJSON(t *testing.T) { + tests := []struct { + name string + jsonInput string + wantKind EvaluationKind + wantSpec interface{} + wantError bool + }{ + { + name: "rolling evaluation with valid data", + jsonInput: `{"kind":"rolling","spec":{"evalWindow":"5m","frequency":"1m"}}`, + wantKind: RollingEvaluation, + wantSpec: RollingWindow{ + EvalWindow: Duration(5 * time.Minute), + Frequency: Duration(1 * time.Minute), + }, + }, + { + name: "cumulative evaluation with valid data", + jsonInput: `{"kind":"cumulative","spec":{"schedule":{"type":"hourly","minute":30},"frequency":"2m","timezone":"UTC"}}`, + wantKind: CumulativeEvaluation, + wantSpec: CumulativeWindow{ + Schedule: CumulativeSchedule{ + Type: ScheduleTypeHourly, + Minute: intPtr(30), + }, + Frequency: Duration(2 * time.Minute), + Timezone: "UTC", + }, + }, + { + name: "rolling evaluation with validation error - zero evalWindow", + jsonInput: `{"kind":"rolling","spec":{"evalWindow":"0s","frequency":"1m"}}`, + wantError: true, + }, + { + name: "rolling evaluation with validation error - zero frequency", + jsonInput: `{"kind":"rolling","spec":{"evalWindow":"5m","frequency":"0s"}}`, + wantError: true, + }, + { + name: "cumulative evaluation with validation error - zero frequency", + jsonInput: `{"kind":"cumulative","spec":{"schedule":{"type":"hourly","minute":30},"frequency":"0s","timezone":"UTC"}}`, + wantError: true, + }, + { + name: "cumulative evaluation with validation error - invalid timezone", + jsonInput: `{"kind":"cumulative","spec":{"schedule":{"type":"daily","hour":9,"minute":30},"frequency":"1m","timezone":"Invalid/Timezone"}}`, + wantError: true, + }, + { + name: "cumulative evaluation with validation error - missing minute for hourly", + jsonInput: `{"kind":"cumulative","spec":{"schedule":{"type":"hourly"},"frequency":"1m","timezone":"UTC"}}`, + wantError: true, + }, + { + name: "unknown evaluation kind", + jsonInput: `{"kind":"unknown","spec":{"evalWindow":"5m","frequency":"1h"}}`, + wantError: true, + }, + { + name: "invalid JSON", + jsonInput: `{"kind":"rolling","spec":invalid}`, + wantError: true, + }, + { + name: "missing kind field", + jsonInput: `{"spec":{"evalWindow":"5m","frequency":"1m"}}`, + wantError: true, + }, + { + name: "missing spec field", + jsonInput: `{"kind":"rolling"}`, + wantError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var envelope EvaluationEnvelope + err := json.Unmarshal([]byte(tt.jsonInput), &envelope) + + if tt.wantError { + if err == nil { + t.Errorf("EvaluationEnvelope.UnmarshalJSON() expected error, got none") + } + return + } + + if err != nil { + t.Fatalf("EvaluationEnvelope.UnmarshalJSON() unexpected error = %v", err) + } + + if envelope.Kind != tt.wantKind { + t.Errorf("EvaluationEnvelope.Kind = %v, want %v", envelope.Kind, tt.wantKind) + } + + // Check spec content based on type + switch tt.wantKind { + case RollingEvaluation: + gotSpec, ok := envelope.Spec.(RollingWindow) + if !ok { + t.Fatalf("Expected RollingWindow spec, got %T", envelope.Spec) + } + wantSpec := tt.wantSpec.(RollingWindow) + if gotSpec.EvalWindow != wantSpec.EvalWindow { + t.Errorf("RollingWindow.EvalWindow = %v, want %v", gotSpec.EvalWindow, wantSpec.EvalWindow) + } + if gotSpec.Frequency != wantSpec.Frequency { + t.Errorf("RollingWindow.Frequency = %v, want %v", gotSpec.Frequency, wantSpec.Frequency) + } + case CumulativeEvaluation: + gotSpec, ok := envelope.Spec.(CumulativeWindow) + if !ok { + t.Fatalf("Expected CumulativeWindow spec, got %T", envelope.Spec) + } + wantSpec := tt.wantSpec.(CumulativeWindow) + if gotSpec.Schedule.Type != wantSpec.Schedule.Type { + t.Errorf("CumulativeWindow.Schedule.Type = %v, want %v", gotSpec.Schedule.Type, wantSpec.Schedule.Type) + } + if (gotSpec.Schedule.Minute == nil) != (wantSpec.Schedule.Minute == nil) || + (gotSpec.Schedule.Minute != nil && wantSpec.Schedule.Minute != nil && *gotSpec.Schedule.Minute != *wantSpec.Schedule.Minute) { + t.Errorf("CumulativeWindow.Schedule.Minute = %v, want %v", gotSpec.Schedule.Minute, wantSpec.Schedule.Minute) + } + if gotSpec.Frequency != wantSpec.Frequency { + t.Errorf("CumulativeWindow.Frequency = %v, want %v", gotSpec.Frequency, wantSpec.Frequency) + } + if gotSpec.Timezone != wantSpec.Timezone { + t.Errorf("CumulativeWindow.Timezone = %v, want %v", gotSpec.Timezone, wantSpec.Timezone) + } + } + }) + } +} From d075ceecba1fb61be5174e8fd432b6dc9f9aceec Mon Sep 17 00:00:00 2001 From: manika-signoz Date: Mon, 15 Sep 2025 18:54:37 +0530 Subject: [PATCH 8/9] chore: copy changes and minor fixes, onboarding hint (#9095) --- .../AboutSigNozQuestions/AboutSigNozQuestions.tsx | 2 +- .../OnboardingQuestionaire/OrgQuestions/OrgQuestions.tsx | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/src/container/OnboardingQuestionaire/AboutSigNozQuestions/AboutSigNozQuestions.tsx b/frontend/src/container/OnboardingQuestionaire/AboutSigNozQuestions/AboutSigNozQuestions.tsx index 5c28f3a2f9c1..2124e995150f 100644 --- a/frontend/src/container/OnboardingQuestionaire/AboutSigNozQuestions/AboutSigNozQuestions.tsx +++ b/frontend/src/container/OnboardingQuestionaire/AboutSigNozQuestions/AboutSigNozQuestions.tsx @@ -98,7 +98,7 @@ export function AboutSigNozQuestions({