diff --git a/.github/workflows/build-and-deploy-prod.yml b/.github/workflows/build-and-deploy-prod.yml index 3e491cd25dcc5..3ca21341d11aa 100644 --- a/.github/workflows/build-and-deploy-prod.yml +++ b/.github/workflows/build-and-deploy-prod.yml @@ -7,6 +7,8 @@ on: push: branches: - master + paths-ignore: + - 'rust/**' jobs: slack: diff --git a/.github/workflows/ci-hog.yml b/.github/workflows/ci-hog.yml index 7e6a5caf94005..db26722eb7e84 100644 --- a/.github/workflows/ci-hog.yml +++ b/.github/workflows/ci-hog.yml @@ -51,7 +51,7 @@ jobs: # If this run wasn't initiated by the bot (meaning: snapshot update) and we've determined # there are backend changes, cancel previous runs - uses: n1hility/cancel-previous-runs@v3 - if: github.actor != 'posthog-bot' && needs.changes.outputs.backend == 'true' + if: github.actor != 'posthog-bot' && needs.changes.outputs.hog == 'true' with: token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/container-images-cd.yml b/.github/workflows/container-images-cd.yml index 9611588bc1700..ec353909ae486 100644 --- a/.github/workflows/container-images-cd.yml +++ b/.github/workflows/container-images-cd.yml @@ -13,6 +13,8 @@ on: push: branches: - master + paths-ignore: + - 'rust/**' workflow_dispatch: jobs: @@ -120,17 +122,22 @@ jobs: - name: Trigger Batch Exports Temporal Worker Cloud deployment if: steps.check_changes_batch_exports_temporal_worker.outputs.changed == 'true' - uses: mvasigh/dispatch-action@main + uses: peter-evans/repository-dispatch@v3 with: token: ${{ steps.deployer.outputs.token }} - repo: charts - owner: PostHog - event_type: temporal_worker_deploy - message: | + repository: PostHog/charts + event-type: commit_state_update + client-payload: | { - "image_tag": "${{ steps.build.outputs.digest }}", - "worker_name": "temporal-worker", - "context": ${{ toJson(github) }} + "values": { + "image": { + "sha": "${{ steps.build.outputs.digest }}" + } + }, + "release": "temporal-worker", + "commit": ${{ toJson(github.event.head_commit) }}, + "repository": ${{ toJson(github.repository) }}, + "labels": ${{ steps.labels.outputs.labels }} } - name: Check for changes that affect general purpose temporal worker @@ -140,17 +147,22 @@ jobs: - name: Trigger General Purpose Temporal Worker Cloud deployment if: steps.check_changes_general_purpose_temporal_worker.outputs.changed == 'true' - uses: mvasigh/dispatch-action@main + uses: peter-evans/repository-dispatch@v3 with: token: ${{ steps.deployer.outputs.token }} - repo: charts - owner: PostHog - event_type: temporal_worker_deploy - message: | + repository: PostHog/charts + event-type: commit_state_update + client-payload: | { - "image_tag": "${{ steps.build.outputs.digest }}", - "worker_name": "temporal-worker-general-purpose", - "context": ${{ toJson(github) }} + "values": { + "image": { + "sha": "${{ steps.build.outputs.digest }}" + } + }, + "release": "temporal-worker-general-purpose", + "commit": ${{ toJson(github.event.head_commit) }}, + "repository": ${{ toJson(github.repository) }}, + "labels": ${{ steps.labels.outputs.labels }} } - name: Check for changes that affect data warehouse temporal worker @@ -160,15 +172,20 @@ jobs: - name: Trigger Data Warehouse Temporal Worker Cloud deployment if: steps.check_changes_data_warehouse_temporal_worker.outputs.changed == 'true' - uses: mvasigh/dispatch-action@main + uses: peter-evans/repository-dispatch@v3 with: token: ${{ steps.deployer.outputs.token }} - repo: charts - owner: PostHog - event_type: temporal_worker_deploy - message: | + repository: PostHog/charts + event-type: commit_state_update + client-payload: | { - "image_tag": "${{ steps.build.outputs.digest }}", - "worker_name": "temporal-worker-data-warehouse", - "context": ${{ toJson(github) }} + "values": { + "image": { + "sha": "${{ steps.build.outputs.digest }}" + } + }, + "release": "temporal-worker-data-warehouse", + "commit": ${{ toJson(github.event.head_commit) }}, + "repository": ${{ toJson(github.repository) }}, + "labels": ${{ steps.labels.outputs.labels }} } diff --git a/rust/.github/workflows/docker-build.yml b/.github/workflows/rust-docker-build.yml similarity index 78% rename from rust/.github/workflows/docker-build.yml rename to .github/workflows/rust-docker-build.yml index 94f36ef0d249b..d44e5d847e601 100644 --- a/rust/.github/workflows/docker-build.yml +++ b/.github/workflows/rust-docker-build.yml @@ -3,8 +3,10 @@ name: Build container images on: workflow_dispatch: push: + paths: + - 'rust/**' branches: - - 'main' + - 'master' jobs: build: @@ -22,9 +24,19 @@ jobs: contents: read # allow reading the repo contents packages: write # allow push to ghcr.io + defaults: + run: + working-directory: rust + steps: - name: Check Out Repo + # Checkout project code + # Use sparse checkout to only select files in rust directory + # Turning off cone mode ensures that files in the project root are not included during checkout uses: actions/checkout@v3 + with: + sparse-checkout: 'rust/' + sparse-checkout-cone-mode: false - name: Set up Depot CLI uses: depot/setup-action@v1 @@ -41,6 +53,7 @@ jobs: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + logout: false - name: Set up QEMU uses: docker/setup-qemu-action@v2 @@ -49,7 +62,7 @@ jobs: id: meta uses: docker/metadata-action@v4 with: - images: ghcr.io/posthog/hog-rs/${{ matrix.image }} + images: ghcr.io/posthog/posthog/${{ matrix.image }} tags: | type=ref,event=pr type=ref,event=branch @@ -65,8 +78,8 @@ jobs: id: docker_build uses: depot/build-push-action@v1 with: - context: ./ - file: ./Dockerfile + context: ./rust/ + file: ./rust/Dockerfile push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} diff --git a/rust/.github/workflows/docker-migrator.yml b/.github/workflows/rust-hook-migrator-docker.yml similarity index 80% rename from rust/.github/workflows/docker-migrator.yml rename to .github/workflows/rust-hook-migrator-docker.yml index 69f14acd83fa8..2dd7c01d015dc 100644 --- a/rust/.github/workflows/docker-migrator.yml +++ b/.github/workflows/rust-hook-migrator-docker.yml @@ -3,8 +3,10 @@ name: Build hook-migrator docker image on: workflow_dispatch: push: + paths: + - 'rust/**' branches: - - 'main' + - 'master' permissions: packages: write @@ -18,9 +20,19 @@ jobs: contents: read # allow reading the repo contents packages: write # allow push to ghcr.io + defaults: + run: + working-directory: rust + steps: - name: Check Out Repo + # Checkout project code + # Use sparse checkout to only select files in rust directory + # Turning off cone mode ensures that files in the project root are not included during checkout uses: actions/checkout@v3 + with: + sparse-checkout: 'rust/' + sparse-checkout-cone-mode: false - name: Set up Depot CLI uses: depot/setup-action@v1 @@ -61,8 +73,8 @@ jobs: id: docker_build_hook_migrator uses: depot/build-push-action@v1 with: - context: ./ - file: ./Dockerfile.migrate + context: ./rust/ + file: ./rust/Dockerfile.migrate push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000000000..c2c379334980e --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,192 @@ +name: Rust + +on: + workflow_dispatch: + push: + branches: [master, main] + pull_request: + +env: + CARGO_TERM_COLOR: always + +jobs: + # Job to decide if we should run rust ci + # See https://github.com/dorny/paths-filter#conditional-execution for more details + changes: + runs-on: ubuntu-latest + timeout-minutes: 5 + if: github.repository == 'PostHog/posthog' + name: Determine need to run rust checks + # Set job outputs to values from filter step + outputs: + rust: ${{ steps.filter.outputs.rust }} + steps: + # For pull requests it's not necessary to checkout the code, but we + # also want this to run on master so we need to checkout + - uses: actions/checkout@v3 + - uses: dorny/paths-filter@v2 + id: filter + with: + filters: | + rust: + # Avoid running rust tests for irrelevant changes + - 'rust/**' + + build: + needs: changes + runs-on: depot-ubuntu-22.04-4 + + defaults: + run: + working-directory: rust + + steps: + # Checkout project code + # Use sparse checkout to only select files in rust directory + # Turning off cone mode ensures that files in the project root are not included during checkout + - uses: actions/checkout@v3 + if: needs.changes.outputs.rust == 'true' + with: + sparse-checkout: 'rust/' + sparse-checkout-cone-mode: false + + - name: Install rust + if: needs.changes.outputs.rust == 'true' + uses: dtolnay/rust-toolchain@1.77 + + - uses: actions/cache@v3 + if: needs.changes.outputs.rust == 'true' + with: + path: | + ~/.cargo/registry + ~/.cargo/git + rust/target + key: ${{ runner.os }}-cargo-release-${{ hashFiles('**/Cargo.lock') }} + + - name: Run cargo build + if: needs.changes.outputs.rust == 'true' + run: cargo build --all --locked --release && find target/release/ -maxdepth 1 -executable -type f | xargs strip + + test: + needs: changes + runs-on: depot-ubuntu-22.04-4 + timeout-minutes: 10 + + defaults: + run: + working-directory: rust + + steps: + # Checkout project code + # Use sparse checkout to only select files in rust directory + # Turning off cone mode ensures that files in the project root are not included during checkout + - uses: actions/checkout@v3 + if: needs.changes.outputs.rust == 'true' + with: + sparse-checkout: 'rust/' + sparse-checkout-cone-mode: false + + - name: Login to DockerHub + if: needs.changes.outputs.rust == 'true' + uses: docker/login-action@v2 + with: + username: posthog + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Setup dependencies + if: needs.changes.outputs.rust == 'true' + run: | + docker compose up kafka redis db echo_server -d --wait + docker compose up setup_test_db + echo "127.0.0.1 kafka" | sudo tee -a /etc/hosts + + - name: Install rust + if: needs.changes.outputs.rust == 'true' + uses: dtolnay/rust-toolchain@1.77 + + - uses: actions/cache@v3 + if: needs.changes.outputs.rust == 'true' + with: + path: | + ~/.cargo/registry + ~/.cargo/git + rust/target + key: ${ runner.os }-cargo-debug-${{ hashFiles('**/Cargo.lock') }} + + - name: Run cargo test + if: needs.changes.outputs.rust == 'true' + run: cargo test --all-features + + linting: + needs: changes + runs-on: depot-ubuntu-22.04-4 + + defaults: + run: + working-directory: rust + + steps: + # Checkout project code + # Use sparse checkout to only select files in rust directory + # Turning off cone mode ensures that files in the project root are not included during checkout + - uses: actions/checkout@v3 + if: needs.changes.outputs.rust == 'true' + with: + sparse-checkout: 'rust/' + sparse-checkout-cone-mode: false + + - name: Install rust + if: needs.changes.outputs.rust == 'true' + uses: dtolnay/rust-toolchain@1.77 + with: + components: clippy,rustfmt + + - uses: actions/cache@v3 + if: needs.changes.outputs.rust == 'true' + with: + path: | + ~/.cargo/registry + ~/.cargo/git + rust/target + key: ${{ runner.os }}-cargo-debug-${{ hashFiles('**/Cargo.lock') }} + + - name: Check format + if: needs.changes.outputs.rust == 'true' + run: cargo fmt -- --check + + - name: Run clippy + if: needs.changes.outputs.rust == 'true' + run: cargo clippy -- -D warnings + + - name: Run cargo check + if: needs.changes.outputs.rust == 'true' + run: cargo check --all-features + + shear: + needs: changes + runs-on: depot-ubuntu-22.04-4 + + defaults: + run: + working-directory: rust + + steps: + # Checkout project code + # Use sparse checkout to only select files in rust directory + # Turning off cone mode ensures that files in the project root are not included during checkout + - uses: actions/checkout@v3 + if: needs.changes.outputs.rust == 'true' + with: + sparse-checkout: 'rust/' + sparse-checkout-cone-mode: false + + - name: Install cargo-binstall + if: needs.changes.outputs.rust == 'true' + uses: cargo-bins/cargo-binstall@main + + - name: Install cargo-shear + if: needs.changes.outputs.rust == 'true' + run: cargo binstall --no-confirm cargo-shear + + - run: cargo shear + if: needs.changes.outputs.rust == 'true' diff --git a/README.md b/README.md index f261c5f98d466..fcdeaa17cdadc 100644 --- a/README.md +++ b/README.md @@ -19,13 +19,15 @@ PostHog Demonstration - See PostHog in action +
See PostHog in action

## PostHog is an all-in-one, open source platform for building better products - Specify events manually, or use autocapture to get started quickly - Analyze data with ready-made visualizations, or do it yourself with SQL +- Only capture properties on the people you want to track, save money when you don't - Gather insights by capturing session replays, console logs, and network monitoring - Improve your product with A/B testing that automatically analyzes performance - Safely roll out features to select users or cohorts with feature flags @@ -38,7 +40,7 @@ PostHog is available with hosting in the EU or US and is fully SOC 2 compliant. - 1 million feature flag requests - 250 survey responses -We're constantly adding new features, with web analytics and data warehouse now in beta! +We're constantly adding new features, with web analytics and data warehouse now in beta! ## Table of Contents @@ -73,7 +75,7 @@ PostHog brings all the tools and data you need to build better products. ### Analytics and optimization tools - **Event-based analytics:** Capture your product's usage [automatically](https://posthog.com/docs/libraries/js#autocapture), or [customize](https://posthog.com/docs/getting-started/install) it to your needs -- **User and group tracking:** Understand the [people](https://posthog.com/manual/persons) and [groups](https://posthog.com/manual/group-analytics) behind the events and track properties about them +- **User and group tracking:** Understand the [people](https://posthog.com/manual/persons) and [groups](https://posthog.com/manual/group-analytics) behind the events and track properties about them when needed - **Data visualizations:** Create and share [graphs](https://posthog.com/docs/features/trends), [funnels](https://posthog.com/docs/features/funnels), [paths](https://posthog.com/docs/features/paths), [retention](https://posthog.com/docs/features/retention), and [dashboards](https://posthog.com/docs/features/dashboards) - **SQL access:** Use [SQL](https://posthog.com/docs/product-analytics/sql) to get a deeper understanding of your users, breakdown information and create completely tailored visualizations - **Session replays:** [Watch videos](https://posthog.com/docs/features/session-recording) of your users' behavior, with fine-grained filters and privacy controls, as well as network monitoring and captured console logs diff --git a/bin/build-schema-python.sh b/bin/build-schema-python.sh index d32c4caedfda9..7937731b55116 100755 --- a/bin/build-schema-python.sh +++ b/bin/build-schema-python.sh @@ -9,7 +9,8 @@ datamodel-codegen \ --input frontend/src/queries/schema.json --input-file-type jsonschema \ --output posthog/schema.py --output-model-type pydantic_v2.BaseModel \ --custom-file-header "# mypy: disable-error-code=\"assignment\"" \ - --set-default-enum-member + --set-default-enum-member --capitalise-enum-members \ + --wrap-string-literal # Format schema.py ruff format posthog/schema.py diff --git a/docker-compose.base.yml b/docker-compose.base.yml index 2edb94dd9bc78..15adbe9d5febe 100644 --- a/docker-compose.base.yml +++ b/docker-compose.base.yml @@ -115,6 +115,13 @@ services: CLICKHOUSE_SECURE: 'false' CLICKHOUSE_VERIFY: 'false' + livestream: + image: 'ghcr.io/posthog/livestream:main' + restart: on-failure + depends_on: + kafka: + condition: service_started + migrate: <<: *worker command: sh -c " diff --git a/docker-compose.dev-full.yml b/docker-compose.dev-full.yml index cdb2eee4ec285..002553728d815 100644 --- a/docker-compose.dev-full.yml +++ b/docker-compose.dev-full.yml @@ -71,6 +71,14 @@ services: - '1080:1080' - '1025:1025' + webhook-tester: + image: tarampampam/webhook-tester:1.1.0 + restart: on-failure + ports: + - '2080:2080' + environment: + - PORT=2080 + worker: extends: file: docker-compose.base.yml diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 25d30840b83ee..f79697c73fbfd 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -95,6 +95,14 @@ services: - '1080:1080' - '1025:1025' + webhook-tester: + image: tarampampam/webhook-tester:1.1.0 + restart: on-failure + ports: + - '2080:2080' + environment: + - LISTEN_PORT=2080 + # Optional capture capture: profiles: ['capture-rs'] @@ -109,6 +117,17 @@ services: - redis - kafka + livestream: + extends: + file: docker-compose.base.yml + service: livestream + environment: + - JWT.TOKEN=${SECRET_KEY} + ports: + - '8666:8080' + volumes: + - ./docker/livestream/configs-dev.yml:/configs/configs.yml + # Temporal containers elasticsearch: extends: diff --git a/ee/clickhouse/models/test/test_cohort.py b/ee/clickhouse/models/test/test_cohort.py index eb2956f1f8078..8af41154c48a5 100644 --- a/ee/clickhouse/models/test/test_cohort.py +++ b/ee/clickhouse/models/test/test_cohort.py @@ -142,9 +142,11 @@ def test_prop_cohort_basic_action(self): query, params = parse_prop_grouped_clauses( team_id=self.team.pk, property_group=filter.property_groups, - person_properties_mode=PersonPropertiesMode.USING_SUBQUERY - if self.team.person_on_events_mode == PersonsOnEventsMode.disabled - else PersonPropertiesMode.DIRECT_ON_EVENTS, + person_properties_mode=( + PersonPropertiesMode.USING_SUBQUERY + if self.team.person_on_events_mode == PersonsOnEventsMode.DISABLED + else PersonPropertiesMode.DIRECT_ON_EVENTS + ), hogql_context=filter.hogql_context, ) final_query = "SELECT uuid FROM events WHERE team_id = %(team_id)s {}".format(query) @@ -197,9 +199,11 @@ def test_prop_cohort_basic_event_days(self): query, params = parse_prop_grouped_clauses( team_id=self.team.pk, property_group=filter.property_groups, - person_properties_mode=PersonPropertiesMode.USING_SUBQUERY - if self.team.person_on_events_mode == PersonsOnEventsMode.disabled - else PersonPropertiesMode.DIRECT_ON_EVENTS, + person_properties_mode=( + PersonPropertiesMode.USING_SUBQUERY + if self.team.person_on_events_mode == PersonsOnEventsMode.DISABLED + else PersonPropertiesMode.DIRECT_ON_EVENTS + ), hogql_context=filter.hogql_context, ) final_query = "SELECT uuid FROM events WHERE team_id = %(team_id)s {}".format(query) @@ -222,9 +226,11 @@ def test_prop_cohort_basic_event_days(self): query, params = parse_prop_grouped_clauses( team_id=self.team.pk, property_group=filter.property_groups, - person_properties_mode=PersonPropertiesMode.USING_SUBQUERY - if self.team.person_on_events_mode == PersonsOnEventsMode.disabled - else PersonPropertiesMode.DIRECT_ON_EVENTS, + person_properties_mode=( + PersonPropertiesMode.USING_SUBQUERY + if self.team.person_on_events_mode == PersonsOnEventsMode.DISABLED + else PersonPropertiesMode.DIRECT_ON_EVENTS + ), hogql_context=filter.hogql_context, ) final_query = "SELECT uuid FROM events WHERE team_id = %(team_id)s {}".format(query) @@ -273,9 +279,11 @@ def test_prop_cohort_basic_action_days(self): query, params = parse_prop_grouped_clauses( team_id=self.team.pk, property_group=filter.property_groups, - person_properties_mode=PersonPropertiesMode.USING_SUBQUERY - if self.team.person_on_events_mode == PersonsOnEventsMode.disabled - else PersonPropertiesMode.DIRECT_ON_EVENTS, + person_properties_mode=( + PersonPropertiesMode.USING_SUBQUERY + if self.team.person_on_events_mode == PersonsOnEventsMode.DISABLED + else PersonPropertiesMode.DIRECT_ON_EVENTS + ), hogql_context=filter.hogql_context, ) final_query = "SELECT uuid FROM events WHERE team_id = %(team_id)s {}".format(query) @@ -294,9 +302,11 @@ def test_prop_cohort_basic_action_days(self): query, params = parse_prop_grouped_clauses( team_id=self.team.pk, property_group=filter.property_groups, - person_properties_mode=PersonPropertiesMode.USING_SUBQUERY - if self.team.person_on_events_mode == PersonsOnEventsMode.disabled - else PersonPropertiesMode.DIRECT_ON_EVENTS, + person_properties_mode=( + PersonPropertiesMode.USING_SUBQUERY + if self.team.person_on_events_mode == PersonsOnEventsMode.DISABLED + else PersonPropertiesMode.DIRECT_ON_EVENTS + ), hogql_context=filter.hogql_context, ) final_query = "SELECT uuid FROM events WHERE team_id = %(team_id)s {}".format(query) diff --git a/ee/clickhouse/queries/enterprise_cohort_query.py b/ee/clickhouse/queries/enterprise_cohort_query.py index 814b61e9a8bf5..72b0ed9bf5e6a 100644 --- a/ee/clickhouse/queries/enterprise_cohort_query.py +++ b/ee/clickhouse/queries/enterprise_cohort_query.py @@ -319,7 +319,7 @@ def _get_sequence_query(self) -> tuple[str, dict[str, Any], str]: event_param_name = f"{self._cohort_pk}_event_ids" - if self.should_pushdown_persons and self._person_on_events_mode != PersonsOnEventsMode.disabled: + if self.should_pushdown_persons and self._person_on_events_mode != PersonsOnEventsMode.DISABLED: person_prop_query, person_prop_params = self._get_prop_groups( self._inner_property_groups, person_properties_mode=PersonPropertiesMode.DIRECT_ON_EVENTS, diff --git a/ee/clickhouse/queries/event_query.py b/ee/clickhouse/queries/event_query.py index 0e16abc780049..977ec53e74314 100644 --- a/ee/clickhouse/queries/event_query.py +++ b/ee/clickhouse/queries/event_query.py @@ -37,7 +37,7 @@ def __init__( extra_event_properties: Optional[list[PropertyName]] = None, extra_person_fields: Optional[list[ColumnName]] = None, override_aggregate_users_by_distinct_id: Optional[bool] = None, - person_on_events_mode: PersonsOnEventsMode = PersonsOnEventsMode.disabled, + person_on_events_mode: PersonsOnEventsMode = PersonsOnEventsMode.DISABLED, **kwargs, ) -> None: if extra_person_fields is None: diff --git a/ee/clickhouse/queries/funnels/funnel_correlation.py b/ee/clickhouse/queries/funnels/funnel_correlation.py index ff69e53d2e01e..efa84347730d7 100644 --- a/ee/clickhouse/queries/funnels/funnel_correlation.py +++ b/ee/clickhouse/queries/funnels/funnel_correlation.py @@ -152,7 +152,7 @@ def __init__( def properties_to_include(self) -> list[str]: props_to_include = [] if ( - self._team.person_on_events_mode != PersonsOnEventsMode.disabled + self._team.person_on_events_mode != PersonsOnEventsMode.DISABLED and self._filter.correlation_type == FunnelCorrelationType.PROPERTIES ): # When dealing with properties, make sure funnel response comes with properties @@ -432,7 +432,7 @@ def get_properties_query(self) -> tuple[str, dict[str, Any]]: return query, params def _get_aggregation_target_join_query(self) -> str: - if self._team.person_on_events_mode == PersonsOnEventsMode.person_id_no_override_properties_on_events: + if self._team.person_on_events_mode == PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS: aggregation_person_join = f""" JOIN funnel_actors as actors ON event.person_id = actors.actor_id @@ -499,7 +499,7 @@ def _get_events_join_query(self) -> str: def _get_aggregation_join_query(self): if self._filter.aggregation_group_type_index is None: - if self._team.person_on_events_mode != PersonsOnEventsMode.disabled and groups_on_events_querying_enabled(): + if self._team.person_on_events_mode != PersonsOnEventsMode.DISABLED and groups_on_events_querying_enabled(): return "", {} person_query, person_query_params = PersonQuery( @@ -519,7 +519,7 @@ def _get_aggregation_join_query(self): return GroupsJoinQuery(self._filter, self._team.pk, join_key="funnel_actors.actor_id").get_join_query() def _get_properties_prop_clause(self): - if self._team.person_on_events_mode != PersonsOnEventsMode.disabled and groups_on_events_querying_enabled(): + if self._team.person_on_events_mode != PersonsOnEventsMode.DISABLED and groups_on_events_querying_enabled(): group_properties_field = f"group{self._filter.aggregation_group_type_index}_properties" aggregation_properties_alias = ( "person_properties" if self._filter.aggregation_group_type_index is None else group_properties_field @@ -546,7 +546,7 @@ def _get_properties_prop_clause(self): param_name = f"property_name_{index}" if self._filter.aggregation_group_type_index is not None: expression, _ = get_property_string_expr( - "groups" if self._team.person_on_events_mode == PersonsOnEventsMode.disabled else "events", + "groups" if self._team.person_on_events_mode == PersonsOnEventsMode.DISABLED else "events", property_name, f"%({param_name})s", aggregation_properties_alias, @@ -554,13 +554,15 @@ def _get_properties_prop_clause(self): ) else: expression, _ = get_property_string_expr( - "person" if self._team.person_on_events_mode == PersonsOnEventsMode.disabled else "events", + "person" if self._team.person_on_events_mode == PersonsOnEventsMode.DISABLED else "events", property_name, f"%({param_name})s", aggregation_properties_alias, - materialised_table_column=aggregation_properties_alias - if self._team.person_on_events_mode != PersonsOnEventsMode.disabled - else "properties", + materialised_table_column=( + aggregation_properties_alias + if self._team.person_on_events_mode != PersonsOnEventsMode.DISABLED + else "properties" + ), ) person_property_params[param_name] = property_name person_property_expressions.append(expression) diff --git a/ee/clickhouse/queries/groups_join_query.py b/ee/clickhouse/queries/groups_join_query.py index 7a3dc46daf993..ddb7d193d6d9b 100644 --- a/ee/clickhouse/queries/groups_join_query.py +++ b/ee/clickhouse/queries/groups_join_query.py @@ -27,7 +27,7 @@ def __init__( team_id: int, column_optimizer: Optional[EnterpriseColumnOptimizer] = None, join_key: Optional[str] = None, - person_on_events_mode: PersonsOnEventsMode = PersonsOnEventsMode.disabled, + person_on_events_mode: PersonsOnEventsMode = PersonsOnEventsMode.DISABLED, ) -> None: self._filter = filter self._team_id = team_id @@ -38,7 +38,7 @@ def __init__( def get_join_query(self) -> tuple[str, dict]: join_queries, params = [], {} - if self._person_on_events_mode != PersonsOnEventsMode.disabled and groups_on_events_querying_enabled(): + if self._person_on_events_mode != PersonsOnEventsMode.DISABLED and groups_on_events_querying_enabled(): return "", {} for group_type_index in self._column_optimizer.group_types_to_query: diff --git a/ee/session_recordings/queries/test/test_session_recording_list_from_filters.py b/ee/session_recordings/queries/test/test_session_recording_list_from_filters.py index 84b378bdf5959..8de7d89abee6b 100644 --- a/ee/session_recordings/queries/test/test_session_recording_list_from_filters.py +++ b/ee/session_recordings/queries/test/test_session_recording_list_from_filters.py @@ -64,7 +64,7 @@ def create_event( True, False, False, - PersonsOnEventsMode.person_id_no_override_properties_on_events, + PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS, { "kperson_filter_pre__0": "rgInternal", "kpersonquery_person_filter_fin__0": "rgInternal", @@ -80,7 +80,7 @@ def create_event( False, False, False, - PersonsOnEventsMode.disabled, + PersonsOnEventsMode.DISABLED, { "kperson_filter_pre__0": "rgInternal", "kpersonquery_person_filter_fin__0": "rgInternal", @@ -96,7 +96,7 @@ def create_event( False, True, False, - PersonsOnEventsMode.person_id_override_properties_on_events, + PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS, { "event_names": [], "event_start_time": mock.ANY, @@ -112,7 +112,7 @@ def create_event( False, True, True, - PersonsOnEventsMode.person_id_override_properties_on_events, + PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS, { "event_end_time": mock.ANY, "event_names": [], diff --git a/ee/session_recordings/queries/test/test_session_recording_list_from_session_replay.py b/ee/session_recordings/queries/test/test_session_recording_list_from_session_replay.py index 797ac453e69e0..b743302f896bf 100644 --- a/ee/session_recordings/queries/test/test_session_recording_list_from_session_replay.py +++ b/ee/session_recordings/queries/test/test_session_recording_list_from_session_replay.py @@ -62,7 +62,7 @@ def create_event( True, False, False, - PersonsOnEventsMode.person_id_no_override_properties_on_events, + PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS, { "kperson_filter_pre__0": "rgInternal", "kpersonquery_person_filter_fin__0": "rgInternal", @@ -78,7 +78,7 @@ def create_event( False, False, False, - PersonsOnEventsMode.disabled, + PersonsOnEventsMode.DISABLED, { "kperson_filter_pre__0": "rgInternal", "kpersonquery_person_filter_fin__0": "rgInternal", @@ -94,7 +94,7 @@ def create_event( False, True, False, - PersonsOnEventsMode.person_id_override_properties_on_events, + PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS, { "event_names": [], "event_start_time": mock.ANY, @@ -110,7 +110,7 @@ def create_event( False, True, True, - PersonsOnEventsMode.person_id_override_properties_on_events, + PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS, { "event_end_time": mock.ANY, "event_names": [], diff --git a/frontend/__snapshots__/scenes-app-insights--funnel-left-to-right-breakdown-edit--light.png b/frontend/__snapshots__/scenes-app-insights--funnel-left-to-right-breakdown-edit--light.png index 47914be1ffc6a..87e964fc8ec9c 100644 Binary files a/frontend/__snapshots__/scenes-app-insights--funnel-left-to-right-breakdown-edit--light.png and b/frontend/__snapshots__/scenes-app-insights--funnel-left-to-right-breakdown-edit--light.png differ diff --git a/frontend/src/layout/navigation-3000/navigationLogic.tsx b/frontend/src/layout/navigation-3000/navigationLogic.tsx index af344d75ed176..b09cc1a3f6948 100644 --- a/frontend/src/layout/navigation-3000/navigationLogic.tsx +++ b/frontend/src/layout/navigation-3000/navigationLogic.tsx @@ -17,6 +17,7 @@ import { IconServer, IconTestTube, IconToggle, + IconWarning, } from '@posthog/icons' import { lemonToast, Spinner } from '@posthog/lemon-ui' import { captureException } from '@sentry/react' @@ -450,6 +451,14 @@ export const navigation3000Logic = kea([ icon: , to: urls.replay(), }, + featureFlags[FEATURE_FLAGS.ERROR_TRACKING] + ? { + identifier: Scene.ErrorTracking, + label: 'Error tracking', + icon: , + to: urls.errorTracking(), + } + : null, featureFlags[FEATURE_FLAGS.HEATMAPS_UI] ? { identifier: Scene.Heatmaps, diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 3f44c3b06eeb0..d0a94739c95f4 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -43,6 +43,7 @@ import { FeatureFlagType, Group, GroupListParams, + HogFunctionType, InsightModel, IntegrationType, ListOrganizationMembersParams, @@ -320,6 +321,14 @@ class ApiRequest { return this.pluginConfig(pluginConfigId, teamId).addPathComponent('logs') } + public hogFunctions(teamId?: TeamType['id']): ApiRequest { + return this.projectsDetail(teamId).addPathComponent('hog_functions') + } + + public hogFunction(id: HogFunctionType['id'], teamId?: TeamType['id']): ApiRequest { + return this.hogFunctions(teamId).addPathComponent(id) + } + // # Actions public actions(teamId?: TeamType['id']): ApiRequest { return this.projectsDetail(teamId).addPathComponent('actions') @@ -1634,6 +1643,24 @@ const api = { }, }, + hogFunctions: { + async listTemplates(): Promise> { + return await new ApiRequest().hogFunctions().get() + }, + async list(): Promise> { + return await new ApiRequest().hogFunctions().get() + }, + async get(id: HogFunctionType['id']): Promise { + return await new ApiRequest().hogFunction(id).get() + }, + async create(data: Partial): Promise { + return await new ApiRequest().hogFunctions().create({ data }) + }, + async update(id: HogFunctionType['id'], data: Partial): Promise { + return await new ApiRequest().hogFunction(id).update({ data }) + }, + }, + annotations: { async get(annotationId: RawAnnotationType['id']): Promise { return await new ApiRequest().annotation(annotationId).get() @@ -1978,6 +2005,12 @@ const api = { async reload(sourceId: ExternalDataStripeSource['id']): Promise { await new ApiRequest().externalDataSource(sourceId).withAction('reload').create() }, + async update( + sourceId: ExternalDataStripeSource['id'], + data: Partial + ): Promise { + return await new ApiRequest().externalDataSource(sourceId).update({ data }) + }, async database_schema( source_type: ExternalDataSourceType, payload: Record diff --git a/frontend/src/lib/components/Cards/InsightCard/InsightCard.scss b/frontend/src/lib/components/Cards/InsightCard/InsightCard.scss index 2773867f90eb8..06a06efbe3ef1 100644 --- a/frontend/src/lib/components/Cards/InsightCard/InsightCard.scss +++ b/frontend/src/lib/components/Cards/InsightCard/InsightCard.scss @@ -51,6 +51,10 @@ border: none; border-radius: 0; } + + .WebAnalyticsDashboard .InsightVizDisplay & { + min-height: var(--insight-viz-min-height); + } } .InsightDetails, diff --git a/frontend/src/lib/components/CodeSnippet/CodeSnippet.tsx b/frontend/src/lib/components/CodeSnippet/CodeSnippet.tsx index ec1a6ca6f1608..cf7033aff1046 100644 --- a/frontend/src/lib/components/CodeSnippet/CodeSnippet.tsx +++ b/frontend/src/lib/components/CodeSnippet/CodeSnippet.tsx @@ -5,7 +5,7 @@ import clsx from 'clsx' import { useValues } from 'kea' import { LemonButton } from 'lib/lemon-ui/LemonButton' import { copyToClipboard } from 'lib/utils/copyToClipboard' -import { CSSProperties, useEffect, useState } from 'react' +import { useEffect, useState } from 'react' import { PrismAsyncLight as SyntaxHighlighter } from 'react-syntax-highlighter' import bash from 'react-syntax-highlighter/dist/esm/languages/prism/bash' import dart from 'react-syntax-highlighter/dist/esm/languages/prism/dart' @@ -81,7 +81,7 @@ export interface CodeSnippetProps { wrap?: boolean compact?: boolean actions?: JSX.Element - style?: CSSProperties + className?: string /** What is being copied. @example 'link' */ thing?: string /** If set, the snippet becomes expandable when there's more than this number of lines. */ @@ -93,7 +93,7 @@ export function CodeSnippet({ language = Language.Text, wrap = false, compact = false, - style, + className, actions, thing = 'snippet', maxLinesWithoutExpansion, @@ -120,8 +120,7 @@ export function CodeSnippet({ } return ( - // eslint-disable-next-line react/forbid-dom-props -
+
{actions} {item.query} @@ -263,7 +263,7 @@ function DebugCHQueries(): JSX.Element { language={Language.JSON} maxLinesWithoutExpansion={0} key={item.query_id} - style={{ fontSize: 12, marginBottom: '0.25rem' }} + className="text-sm mb-2" > {JSON.stringify(event, null, 2)} diff --git a/frontend/src/lib/components/CommandPalette/commandPaletteLogic.tsx b/frontend/src/lib/components/CommandPalette/commandPaletteLogic.tsx index 77e9e2c33a701..8084a2a07d650 100644 --- a/frontend/src/lib/components/CommandPalette/commandPaletteLogic.tsx +++ b/frontend/src/lib/components/CommandPalette/commandPaletteLogic.tsx @@ -38,6 +38,7 @@ import { IconTrends, IconUnlock, IconUserPaths, + IconWarning, IconX, } from '@posthog/icons' import { Parser } from 'expr-eval' @@ -581,6 +582,17 @@ export const commandPaletteLogic = kea([ }, ] : []), + ...(values.featureFlags[FEATURE_FLAGS.ERROR_TRACKING] + ? [ + { + icon: IconWarning, + display: 'Go to Error tracking', + executor: () => { + push(urls.errorTracking()) + }, + }, + ] + : []), { display: 'Go to Session replay', icon: IconRewindPlay, diff --git a/frontend/src/lib/components/Errors/ErrorDisplay.stories.tsx b/frontend/src/lib/components/Errors/ErrorDisplay.stories.tsx index 86915996ae5f6..b0aa04cc27800 100644 --- a/frontend/src/lib/components/Errors/ErrorDisplay.stories.tsx +++ b/frontend/src/lib/components/Errors/ErrorDisplay.stories.tsx @@ -1,7 +1,7 @@ import { Meta } from '@storybook/react' import { ErrorDisplay } from 'lib/components/Errors/ErrorDisplay' -import { EventType, RecordingEventType } from '~/types' +import { EventType } from '~/types' const meta: Meta = { title: 'Components/Errors/Error Display', @@ -9,104 +9,95 @@ const meta: Meta = { } export default meta -function errorEvent(properties: Record): EventType | RecordingEventType { +function errorProperties(properties: Record): EventType['properties'] { return { - id: '12345', - elements: [], - uuid: '018880b6-b781-0008-a2e5-629b2624fd2f', - event: '$exception', - properties: { - $os: 'Windows', - $os_version: '10.0', - $browser: 'Chrome', - $device_type: 'Desktop', - $current_url: 'https://app.posthog.com/home', - $host: 'app.posthog.com', - $pathname: '/home', - $browser_version: 113, - $browser_language: 'es-ES', - $screen_height: 1080, - $screen_width: 1920, - $viewport_height: 929, - $viewport_width: 1920, - $lib: 'web', - $lib_version: '1.63.3', - distinct_id: 'iOizUPH4RH65nZjvGVBz5zZUmwdHvq2mxzNySQqqYkG', - $device_id: '186144e7357245-0cfe8bf1b5b877-26021051-1fa400-186144e7358d3', - $active_feature_flags: ['are-the-flags', 'important-for-the-error'], - $feature_flag_payloads: { - 'are-the-flags': '{\n "flag": "payload"\n}', - }, - $user_id: 'iOizUPH4RH65nZjvGVBz5zZUmwdHvq2mxzNySQqqYkG', - $groups: { - project: '00000000-0000-0000-1847-88f0ffa23444', - organization: '00000000-0000-0000-a050-5d4557279956', - customer: 'the-customer', - instance: 'https://app.posthog.com', - }, - $exception_message: 'ResizeObserver loop limit exceeded', - $exception_type: 'Error', - $exception_personURL: 'https://app.posthog.com/person/the-person-id', - $sentry_event_id: 'id-from-the-sentry-integration', - $sentry_exception: { - values: [ - { - value: 'ResizeObserver loop limit exceeded', - type: 'Error', - mechanism: { - type: 'onerror', - handled: false, - synthetic: true, - }, - stacktrace: { - frames: [ - { - colno: 0, - filename: 'https://app.posthog.com/home', - function: '?', - in_app: true, - lineno: 0, - }, - ], - }, + $os: 'Windows', + $os_version: '10.0', + $browser: 'Chrome', + $device_type: 'Desktop', + $current_url: 'https://app.posthog.com/home', + $host: 'app.posthog.com', + $pathname: '/home', + $browser_version: 113, + $browser_language: 'es-ES', + $screen_height: 1080, + $screen_width: 1920, + $viewport_height: 929, + $viewport_width: 1920, + $lib: 'web', + $lib_version: '1.63.3', + distinct_id: 'iOizUPH4RH65nZjvGVBz5zZUmwdHvq2mxzNySQqqYkG', + $device_id: '186144e7357245-0cfe8bf1b5b877-26021051-1fa400-186144e7358d3', + $active_feature_flags: ['are-the-flags', 'important-for-the-error'], + $feature_flag_payloads: { + 'are-the-flags': '{\n "flag": "payload"\n}', + }, + $user_id: 'iOizUPH4RH65nZjvGVBz5zZUmwdHvq2mxzNySQqqYkG', + $groups: { + project: '00000000-0000-0000-1847-88f0ffa23444', + organization: '00000000-0000-0000-a050-5d4557279956', + customer: 'the-customer', + instance: 'https://app.posthog.com', + }, + $exception_message: 'ResizeObserver loop limit exceeded', + $exception_type: 'Error', + $exception_personURL: 'https://app.posthog.com/person/the-person-id', + $sentry_event_id: 'id-from-the-sentry-integration', + $sentry_exception: { + values: [ + { + value: 'ResizeObserver loop limit exceeded', + type: 'Error', + mechanism: { + type: 'onerror', + handled: false, + synthetic: true, }, - ], - }, - $sentry_exception_message: 'ResizeObserver loop limit exceeded', - $sentry_exception_type: 'Error', - $sentry_tags: { - 'PostHog Person URL': 'https://app.posthog.com/person/the-person-id', - 'PostHog Recording URL': 'https://app.posthog.com/replay/the-session-id?t=866', - }, - $sentry_url: - 'https://sentry.io/organizations/posthog/issues/?project=the-sentry-project-id&query=the-sentry-id', - $session_id: 'the-session-id', - $window_id: 'the-window-id', - $pageview_id: 'the-pageview-id', - $sent_at: '2023-06-03T10:03:57.787000+00:00', - $geoip_city_name: 'Whoville', - $geoip_country_name: 'Wholand', - $geoip_country_code: 'WH', - $geoip_continent_name: 'Mystery', - $geoip_continent_code: 'MY', - $geoip_latitude: -30.5023, - $geoip_longitude: -71.1545, - $geoip_time_zone: 'UTC', - $lib_version__major: 1, - $lib_version__minor: 63, - $lib_version__patch: 3, - ...properties, + stacktrace: { + frames: [ + { + colno: 0, + filename: 'https://app.posthog.com/home', + function: '?', + in_app: true, + lineno: 0, + }, + ], + }, + }, + ], + }, + $sentry_exception_message: 'ResizeObserver loop limit exceeded', + $sentry_exception_type: 'Error', + $sentry_tags: { + 'PostHog Person URL': 'https://app.posthog.com/person/the-person-id', + 'PostHog Recording URL': 'https://app.posthog.com/replay/the-session-id?t=866', }, - timestamp: '2023-06-03T03:03:57.316-07:00', - distinct_id: 'the-distinct-id', - elements_chain: '', + $sentry_url: + 'https://sentry.io/organizations/posthog/issues/?project=the-sentry-project-id&query=the-sentry-id', + $session_id: 'the-session-id', + $window_id: 'the-window-id', + $pageview_id: 'the-pageview-id', + $sent_at: '2023-06-03T10:03:57.787000+00:00', + $geoip_city_name: 'Whoville', + $geoip_country_name: 'Wholand', + $geoip_country_code: 'WH', + $geoip_continent_name: 'Mystery', + $geoip_continent_code: 'MY', + $geoip_latitude: -30.5023, + $geoip_longitude: -71.1545, + $geoip_time_zone: 'UTC', + $lib_version__major: 1, + $lib_version__minor: 63, + $lib_version__patch: 3, + ...properties, } } export function ResizeObserverLoopLimitExceeded(): JSX.Element { return ( ) } } -export function ErrorDisplay({ event }: { event: EventType | RecordingEventType }): JSX.Element { - if (event.event !== '$exception') { - return <>Unknown type of error - } - +export function ErrorDisplay({ eventProperties }: { eventProperties: EventType['properties'] }): JSX.Element { const { $exception_type, $exception_message, @@ -175,7 +171,7 @@ export function ErrorDisplay({ event }: { event: EventType | RecordingEventType $sentry_url, $exception_stack_trace_raw, $level, - } = getExceptionPropertiesFrom(event.properties) + } = getExceptionPropertiesFrom(eventProperties) return (
diff --git a/frontend/src/lib/components/PropertyFilters/components/PropertyValue.tsx b/frontend/src/lib/components/PropertyFilters/components/PropertyValue.tsx index 673bd426629bf..427ad6daf6b5a 100644 --- a/frontend/src/lib/components/PropertyFilters/components/PropertyValue.tsx +++ b/frontend/src/lib/components/PropertyFilters/components/PropertyValue.tsx @@ -128,7 +128,7 @@ export function PropertyValue({ loading={options[propertyKey]?.status === 'loading'} value={formattedValues} mode={isMultiSelect ? 'multiple' : 'single'} - allowCustomValues + allowCustomValues={options[propertyKey]?.allowCustomValues} onChange={(nextVal) => (isMultiSelect ? setValue(nextVal) : setValue(nextVal[0]))} onInputChange={onSearchTextChange} placeholder={placeholder} diff --git a/frontend/src/lib/components/PropertyFilters/utils.ts b/frontend/src/lib/components/PropertyFilters/utils.ts index 504c32e178748..da753040497ca 100644 --- a/frontend/src/lib/components/PropertyFilters/utils.ts +++ b/frontend/src/lib/components/PropertyFilters/utils.ts @@ -27,7 +27,7 @@ import { PropertyGroupFilterValue, PropertyOperator, PropertyType, - RecordingDurationFilter, + RecordingPropertyFilter, SessionPropertyFilter, } from '~/types' @@ -89,22 +89,21 @@ export function convertPropertyGroupToProperties( return properties } -export const PROPERTY_FILTER_TYPE_TO_TAXONOMIC_FILTER_GROUP_TYPE: Omit< - Record, - PropertyFilterType.Recording // Recording filters are not part of the taxonomic filter, only Replay-specific UI -> = { - [PropertyFilterType.Meta]: TaxonomicFilterGroupType.Metadata, - [PropertyFilterType.Person]: TaxonomicFilterGroupType.PersonProperties, - [PropertyFilterType.Event]: TaxonomicFilterGroupType.EventProperties, - [PropertyFilterType.Feature]: TaxonomicFilterGroupType.EventFeatureFlags, - [PropertyFilterType.Cohort]: TaxonomicFilterGroupType.Cohorts, - [PropertyFilterType.Element]: TaxonomicFilterGroupType.Elements, - [PropertyFilterType.Session]: TaxonomicFilterGroupType.SessionProperties, - [PropertyFilterType.HogQL]: TaxonomicFilterGroupType.HogQLExpression, - [PropertyFilterType.Group]: TaxonomicFilterGroupType.GroupsPrefix, - [PropertyFilterType.DataWarehouse]: TaxonomicFilterGroupType.DataWarehouse, - [PropertyFilterType.DataWarehousePersonProperty]: TaxonomicFilterGroupType.DataWarehousePersonProperties, -} +export const PROPERTY_FILTER_TYPE_TO_TAXONOMIC_FILTER_GROUP_TYPE: Record = + { + [PropertyFilterType.Meta]: TaxonomicFilterGroupType.Metadata, + [PropertyFilterType.Person]: TaxonomicFilterGroupType.PersonProperties, + [PropertyFilterType.Event]: TaxonomicFilterGroupType.EventProperties, + [PropertyFilterType.Feature]: TaxonomicFilterGroupType.EventFeatureFlags, + [PropertyFilterType.Cohort]: TaxonomicFilterGroupType.Cohorts, + [PropertyFilterType.Element]: TaxonomicFilterGroupType.Elements, + [PropertyFilterType.Session]: TaxonomicFilterGroupType.SessionProperties, + [PropertyFilterType.HogQL]: TaxonomicFilterGroupType.HogQLExpression, + [PropertyFilterType.Group]: TaxonomicFilterGroupType.GroupsPrefix, + [PropertyFilterType.DataWarehouse]: TaxonomicFilterGroupType.DataWarehouse, + [PropertyFilterType.DataWarehousePersonProperty]: TaxonomicFilterGroupType.DataWarehousePersonProperties, + [PropertyFilterType.Recording]: TaxonomicFilterGroupType.Replay, + } export function formatPropertyLabel( item: Record, @@ -200,7 +199,7 @@ export function isElementPropertyFilter(filter?: AnyFilterLike | null): filter i export function isSessionPropertyFilter(filter?: AnyFilterLike | null): filter is SessionPropertyFilter { return filter?.type === PropertyFilterType.Session } -export function isRecordingDurationFilter(filter?: AnyFilterLike | null): filter is RecordingDurationFilter { +export function isRecordingPropertyFilter(filter?: AnyFilterLike | null): filter is RecordingPropertyFilter { return filter?.type === PropertyFilterType.Recording } export function isGroupPropertyFilter(filter?: AnyFilterLike | null): filter is GroupPropertyFilter { @@ -223,7 +222,7 @@ export function isAnyPropertyfilter(filter?: AnyFilterLike | null): filter is An isElementPropertyFilter(filter) || isSessionPropertyFilter(filter) || isCohortPropertyFilter(filter) || - isRecordingDurationFilter(filter) || + isRecordingPropertyFilter(filter) || isFeaturePropertyFilter(filter) || isGroupPropertyFilter(filter) ) @@ -236,7 +235,7 @@ export function isPropertyFilterWithOperator( | PersonPropertyFilter | ElementPropertyFilter | SessionPropertyFilter - | RecordingDurationFilter + | RecordingPropertyFilter | FeaturePropertyFilter | GroupPropertyFilter | DataWarehousePropertyFilter { @@ -246,7 +245,7 @@ export function isPropertyFilterWithOperator( isPersonPropertyFilter(filter) || isElementPropertyFilter(filter) || isSessionPropertyFilter(filter) || - isRecordingDurationFilter(filter) || + isRecordingPropertyFilter(filter) || isFeaturePropertyFilter(filter) || isGroupPropertyFilter(filter) || isDataWarehousePropertyFilter(filter)) @@ -345,6 +344,10 @@ export function taxonomicFilterTypeToPropertyFilterType( return PropertyFilterType.DataWarehousePersonProperty } + if (filterType == TaxonomicFilterGroupType.Replay) { + return PropertyFilterType.Recording + } + return Object.entries(propertyFilterMapping).find(([, v]) => v === filterType)?.[0] as | PropertyFilterType | undefined diff --git a/frontend/src/lib/components/SocialLoginButton/SocialLoginButton.tsx b/frontend/src/lib/components/SocialLoginButton/SocialLoginButton.tsx index ea54e1ddfa2f7..6edef668ef432 100644 --- a/frontend/src/lib/components/SocialLoginButton/SocialLoginButton.tsx +++ b/frontend/src/lib/components/SocialLoginButton/SocialLoginButton.tsx @@ -39,10 +39,10 @@ function SocialLoginLink({ provider, extraQueryParams, children }: SocialLoginLi interface SocialLoginButtonProps { provider: SSOProvider - redirectQueryParams?: Record + extraQueryParams?: Record } -export function SocialLoginButton({ provider, redirectQueryParams }: SocialLoginButtonProps): JSX.Element | null { +export function SocialLoginButton({ provider, extraQueryParams }: SocialLoginButtonProps): JSX.Element | null { const { preflight } = useValues(preflightLogic) if (!preflight?.available_social_auth_providers[provider]) { @@ -50,7 +50,7 @@ export function SocialLoginButton({ provider, redirectQueryParams }: SocialLogin } return ( - + }> {SSO_PROVIDER_NAMES[provider]} @@ -65,7 +65,7 @@ interface SocialLoginButtonsProps { className?: string topDivider?: boolean bottomDivider?: boolean - redirectQueryParams?: Record + extraQueryParams?: Record } export function SocialLoginButtons({ @@ -109,14 +109,19 @@ export function SocialLoginButtons({ ) } -interface SSOEnforcedLoginButtonProps extends Partial { - provider: SSOProvider - email: string -} +type SSOEnforcedLoginButtonProps = SocialLoginButtonProps & + Partial & { + email: string + } -export function SSOEnforcedLoginButton({ provider, email, ...props }: SSOEnforcedLoginButtonProps): JSX.Element { +export function SSOEnforcedLoginButton({ + provider, + email, + extraQueryParams, + ...props +}: SSOEnforcedLoginButtonProps): JSX.Element { return ( - + ([ @@ -340,13 +341,14 @@ export const supportLogic = kea([ values.sendSupportRequest.kind ?? '', values.sendSupportRequest.target_area ?? '', values.sendSupportRequest.severity_level ?? '', + values.isEmailFormOpen ?? 'false', ].join(':') if (panelOptions !== ':') { actions.setSidePanelOptions(panelOptions) } }, - openSupportForm: async ({ name, email, kind, target_area, severity_level, message }) => { + openSupportForm: async ({ name, email, isEmailFormOpen, kind, target_area, severity_level, message }) => { let area = target_area ?? getURLPathToTargetArea(window.location.pathname) if (!userLogic.values.user) { area = 'login' @@ -361,6 +363,12 @@ export const supportLogic = kea([ message: message ?? '', }) + if (isEmailFormOpen === 'true' || isEmailFormOpen === true) { + actions.openEmailForm() + } else { + actions.closeEmailForm() + } + if (values.sidePanelAvailable) { const panelOptions = [kind ?? '', area ?? ''].join(':') actions.openSidePanel(SidePanelTab.Support, panelOptions === ':' ? undefined : panelOptions) @@ -509,12 +517,13 @@ export const supportLogic = kea([ const [panel, ...panelOptions] = (hashParams['panel'] ?? '').split(':') if (panel === SidePanelTab.Support) { - const [kind, area, severity] = panelOptions + const [kind, area, severity, isEmailFormOpen] = panelOptions actions.openSupportForm({ kind: Object.keys(SUPPORT_KIND_TO_SUBJECT).includes(kind) ? kind : null, target_area: Object.keys(TARGET_AREA_TO_NAME).includes(area) ? area : null, severity_level: Object.keys(SEVERITY_LEVEL_TO_NAME).includes(severity) ? severity : null, + isEmailFormOpen: isEmailFormOpen ?? 'false', }) return } diff --git a/frontend/src/lib/components/TaxonomicFilter/InfiniteSelectResults.tsx b/frontend/src/lib/components/TaxonomicFilter/InfiniteSelectResults.tsx index 89bf8bbd9b2f3..f9579316b153f 100644 --- a/frontend/src/lib/components/TaxonomicFilter/InfiniteSelectResults.tsx +++ b/frontend/src/lib/components/TaxonomicFilter/InfiniteSelectResults.tsx @@ -68,7 +68,7 @@ export function InfiniteSelectResults({ selectItem(activeTaxonomicGroup, newValue, newValue)} + onChange={(newValue, item) => selectItem(activeTaxonomicGroup, newValue, item)} /> ) : ( diff --git a/frontend/src/lib/components/TaxonomicFilter/InlineHogQLEditor.tsx b/frontend/src/lib/components/TaxonomicFilter/InlineHogQLEditor.tsx index afc7919bbe72b..b90fe2aadf5d9 100644 --- a/frontend/src/lib/components/TaxonomicFilter/InlineHogQLEditor.tsx +++ b/frontend/src/lib/components/TaxonomicFilter/InlineHogQLEditor.tsx @@ -5,7 +5,7 @@ import { AnyDataNode } from '~/queries/schema' export interface InlineHogQLEditorProps { value?: TaxonomicFilterValue - onChange: (value: TaxonomicFilterValue) => void + onChange: (value: TaxonomicFilterValue, item?: any) => void metadataSource?: AnyDataNode } diff --git a/frontend/src/lib/components/TaxonomicFilter/taxonomicFilterLogic.tsx b/frontend/src/lib/components/TaxonomicFilter/taxonomicFilterLogic.tsx index 8b328a689d389..4c97fc48fc3e8 100644 --- a/frontend/src/lib/components/TaxonomicFilter/taxonomicFilterLogic.tsx +++ b/frontend/src/lib/components/TaxonomicFilter/taxonomicFilterLogic.tsx @@ -22,6 +22,7 @@ import { dataWarehouseSceneLogic } from 'scenes/data-warehouse/external/dataWare import { experimentsLogic } from 'scenes/experiments/experimentsLogic' import { featureFlagsLogic } from 'scenes/feature-flags/featureFlagsLogic' import { groupDisplayId } from 'scenes/persons/GroupActorDisplay' +import { ReplayTaxonomicFilters } from 'scenes/session-recordings/filters/ReplayTaxonomicFilters' import { teamLogic } from 'scenes/teamLogic' import { actionsModel } from '~/models/actionsModel' @@ -506,6 +507,13 @@ export const taxonomicFilterLogic = kea([ getPopoverHeader: () => 'HogQL', componentProps: { metadataSource }, }, + { + name: 'Replay', + searchPlaceholder: 'Replay', + type: TaxonomicFilterGroupType.Replay, + render: ReplayTaxonomicFilters, + getPopoverHeader: () => 'Replay', + }, ...groupAnalyticsTaxonomicGroups, ...groupAnalyticsTaxonomicGroupNames, ] diff --git a/frontend/src/lib/components/TaxonomicFilter/types.ts b/frontend/src/lib/components/TaxonomicFilter/types.ts index 787112ca04ea1..f9743bb18764e 100644 --- a/frontend/src/lib/components/TaxonomicFilter/types.ts +++ b/frontend/src/lib/components/TaxonomicFilter/types.ts @@ -47,7 +47,7 @@ export type TaxonomicFilterValue = string | number | null export type TaxonomicFilterRender = (props: { value?: TaxonomicFilterValue - onChange: (value: TaxonomicFilterValue) => void + onChange: (value: TaxonomicFilterValue, item: any) => void }) => JSX.Element | null export interface TaxonomicFilterGroup { @@ -108,6 +108,8 @@ export enum TaxonomicFilterGroupType { SessionProperties = 'session_properties', HogQLExpression = 'hogql_expression', Notebooks = 'notebooks', + // Misc + Replay = 'replay', } export interface InfiniteListLogicProps extends TaxonomicFilterLogicProps { diff --git a/frontend/src/lib/components/TimeSensitiveAuthentication/TimeSensitiveAuthentication.tsx b/frontend/src/lib/components/TimeSensitiveAuthentication/TimeSensitiveAuthentication.tsx index 6188a9f443bb7..f977f943754e7 100644 --- a/frontend/src/lib/components/TimeSensitiveAuthentication/TimeSensitiveAuthentication.tsx +++ b/frontend/src/lib/components/TimeSensitiveAuthentication/TimeSensitiveAuthentication.tsx @@ -21,6 +21,10 @@ export function TimeSensitiveAuthenticationModal(): JSX.Element { const ssoEnforcement = precheckResponse?.sso_enforcement const showPassword = !ssoEnforcement && user?.has_password + const extraQueryParams = { + next: window.location.pathname, + } + return ( - + ) : showPassword ? ( {precheckResponse?.saml_available ? ( - + ) : null}
) : null} diff --git a/frontend/src/lib/components/UniversalFilters/UniversalFilters.stories.tsx b/frontend/src/lib/components/UniversalFilters/UniversalFilters.stories.tsx index d1019f26cf5b0..0fb9248356ff0 100644 --- a/frontend/src/lib/components/UniversalFilters/UniversalFilters.stories.tsx +++ b/frontend/src/lib/components/UniversalFilters/UniversalFilters.stories.tsx @@ -58,8 +58,9 @@ export const Default: StoryFn = ({ group }) => { void - taxonomicEntityFilterGroupTypes: TaxonomicFilterGroupType[] - taxonomicPropertyFilterGroupTypes: TaxonomicFilterGroupType[] + taxonomicGroupTypes: TaxonomicFilterGroupType[] children?: React.ReactNode } @@ -35,8 +34,7 @@ function UniversalFilters({ rootKey, group = null, onChange, - taxonomicEntityFilterGroupTypes, - taxonomicPropertyFilterGroupTypes, + taxonomicGroupTypes, children, }: UniversalFiltersProps): JSX.Element { return ( @@ -46,8 +44,7 @@ function UniversalFilters({ rootKey, group, onChange, - taxonomicEntityFilterGroupTypes, - taxonomicPropertyFilterGroupTypes, + taxonomicGroupTypes, }} > {children} @@ -64,8 +61,7 @@ function Group({ index: number children: React.ReactNode }): JSX.Element { - const { rootKey, taxonomicEntityFilterGroupTypes, taxonomicPropertyFilterGroupTypes } = - useValues(universalFiltersLogic) + const { rootKey, taxonomicGroupTypes } = useValues(universalFiltersLogic) const { replaceGroupValue } = useActions(universalFiltersLogic) return ( @@ -74,8 +70,7 @@ function Group({ rootKey={`${rootKey}.group_${index}`} group={group} onChange={(group) => replaceGroupValue(index, group)} - taxonomicEntityFilterGroupTypes={taxonomicEntityFilterGroupTypes} - taxonomicPropertyFilterGroupTypes={taxonomicPropertyFilterGroupTypes} + taxonomicGroupTypes={taxonomicGroupTypes} > {children} diff --git a/frontend/src/lib/components/UniversalFilters/universalFiltersLogic.test.ts b/frontend/src/lib/components/UniversalFilters/universalFiltersLogic.test.ts index 448647033b49c..82b52b2e38053 100644 --- a/frontend/src/lib/components/UniversalFilters/universalFiltersLogic.test.ts +++ b/frontend/src/lib/components/UniversalFilters/universalFiltersLogic.test.ts @@ -32,8 +32,9 @@ describe('universalFiltersLogic', () => { logic = universalFiltersLogic({ rootKey: 'test', group: defaultFilter, - taxonomicEntityFilterGroupTypes: [TaxonomicFilterGroupType.Events, TaxonomicFilterGroupType.Actions], - taxonomicPropertyFilterGroupTypes: [ + taxonomicGroupTypes: [ + TaxonomicFilterGroupType.Events, + TaxonomicFilterGroupType.Actions, TaxonomicFilterGroupType.EventProperties, TaxonomicFilterGroupType.PersonProperties, ], diff --git a/frontend/src/lib/components/UniversalFilters/universalFiltersLogic.ts b/frontend/src/lib/components/UniversalFilters/universalFiltersLogic.ts index f6242e3d67447..9f40b436c3e3a 100644 --- a/frontend/src/lib/components/UniversalFilters/universalFiltersLogic.ts +++ b/frontend/src/lib/components/UniversalFilters/universalFiltersLogic.ts @@ -6,7 +6,7 @@ import { import { taxonomicFilterGroupTypeToEntityType } from 'scenes/insights/filters/ActionFilter/ActionFilterRow/ActionFilterRow' import { propertyDefinitionsModel } from '~/models/propertyDefinitionsModel' -import { ActionFilter, FilterLogicalOperator } from '~/types' +import { ActionFilter, FilterLogicalOperator, PropertyFilterType } from '~/types' import { TaxonomicFilterGroup, TaxonomicFilterGroupType, TaxonomicFilterValue } from '../TaxonomicFilter/types' import { UniversalFiltersGroup, UniversalFiltersGroupValue } from './UniversalFilters' @@ -26,8 +26,7 @@ export type UniversalFiltersLogicProps = { rootKey: string group: UniversalFiltersGroup | null onChange: (group: UniversalFiltersGroup) => void - taxonomicEntityFilterGroupTypes: TaxonomicFilterGroupType[] - taxonomicPropertyFilterGroupTypes: TaxonomicFilterGroupType[] + taxonomicGroupTypes: TaxonomicFilterGroupType[] } export const universalFiltersLogic = kea([ @@ -50,7 +49,11 @@ export const universalFiltersLogic = kea([ }), removeGroupValue: (index: number) => ({ index }), - addGroupFilter: (taxonomicGroup: TaxonomicFilterGroup, propertyKey: TaxonomicFilterValue, item: any) => ({ + addGroupFilter: ( + taxonomicGroup: TaxonomicFilterGroup, + propertyKey: TaxonomicFilterValue, + item: { propertyFilterType?: PropertyFilterType; name?: string } + ) => ({ taxonomicGroup, propertyKey, item, @@ -83,11 +86,20 @@ export const universalFiltersLogic = kea([ selectors({ rootKey: [(_, p) => [p.rootKey], (rootKey) => rootKey], - taxonomicEntityFilterGroupTypes: [(_, p) => [p.taxonomicEntityFilterGroupTypes], (types) => types], - taxonomicPropertyFilterGroupTypes: [(_, p) => [p.taxonomicPropertyFilterGroupTypes], (types) => types], - taxonomicGroupTypes: [ - (_, p) => [p.taxonomicEntityFilterGroupTypes, p.taxonomicPropertyFilterGroupTypes], - (entityTypes, propertyTypes) => [...entityTypes, ...propertyTypes], + taxonomicGroupTypes: [(_, p) => [p.taxonomicGroupTypes], (types) => types], + taxonomicPropertyFilterGroupTypes: [ + (_, p) => [p.taxonomicGroupTypes], + (types) => + types.filter((t) => + [ + TaxonomicFilterGroupType.EventProperties, + TaxonomicFilterGroupType.PersonProperties, + TaxonomicFilterGroupType.EventFeatureFlags, + TaxonomicFilterGroupType.Cohorts, + TaxonomicFilterGroupType.Elements, + TaxonomicFilterGroupType.HogQLExpression, + ].includes(t) + ), ], }), @@ -112,7 +124,7 @@ export const universalFiltersLogic = kea([ newValues.push(newPropertyFilter) } else { - const entityType = item.PropertyFilterType ?? taxonomicFilterGroupTypeToEntityType(taxonomicGroup.type) + const entityType = taxonomicFilterGroupTypeToEntityType(taxonomicGroup.type) if (entityType) { const newEntityFilter: ActionFilter = { id: propertyKey, diff --git a/frontend/src/lib/constants.tsx b/frontend/src/lib/constants.tsx index cdca3add1b2cd..07e8c6d51b86f 100644 --- a/frontend/src/lib/constants.tsx +++ b/frontend/src/lib/constants.tsx @@ -167,6 +167,7 @@ export const FEATURE_FLAGS = { APPS_AND_EXPORTS_UI: 'apps-and-exports-ui', // owner: @benjackwhite HOGQL_INSIGHT_LIVE_COMPARE: 'hogql-insight-live-compare', // owner: @mariusandra HOGQL_DASHBOARD_CARDS: 'hogql-dashboard-cards', // owner: @thmsobrmlr + HOGQL_DASHBOARD_ASYNC: 'hogql-dashboard-async', // owner: @webjunkie WEBHOOKS_DENYLIST: 'webhooks-denylist', // owner: #team-pipeline PERSONS_HOGQL_QUERY: 'persons-hogql-query', // owner: @mariusandra PIPELINE_UI: 'pipeline-ui', // owner: #team-pipeline @@ -205,9 +206,13 @@ export const FEATURE_FLAGS = { SESSION_REPLAY_NETWORK_VIEW: 'session-replay-network-view', // owner: #team-replay SETTINGS_PERSONS_JOIN_MODE: 'settings-persons-join-mode', // owner: @robbie-c HOG: 'hog', // owner: @mariusandra + HOG_FUNCTIONS: 'hog-functions', // owner: #team-cdp PERSONLESS_EVENTS_NOT_SUPPORTED: 'personless-events-not-supported', // owner: @raquelmsmith + SESSION_REPLAY_UNIVERSAL_FILTERS: 'session-replay-universal-filters', // owner: #team-replay ALERTS: 'alerts', // owner: github.com/nikitaevg + ERROR_TRACKING: 'error-tracking', // owner: #team-replay SETTINGS_BOUNCE_RATE_PAGE_VIEW_MODE: 'settings-bounce-rate-page-view-mode', // owner: @robbie-c + SURVEYS_BRANCHING_LOGIC: 'surveys-branching-logic', // owner: @jurajmajerik #team-feature-success } as const export type FeatureFlagKey = (typeof FEATURE_FLAGS)[keyof typeof FEATURE_FLAGS] diff --git a/frontend/src/lib/taxonomy.tsx b/frontend/src/lib/taxonomy.tsx index 59abaaa055fca..17fd94ac793de 100644 --- a/frontend/src/lib/taxonomy.tsx +++ b/frontend/src/lib/taxonomy.tsx @@ -1075,6 +1075,17 @@ export const CORE_FILTER_DEFINITIONS_BY_GROUP = { description: 'Specified group key', }, }, + replay: { + console_log_level: { + label: 'Log level', + description: 'Level of console logs captured', + examples: ['info', 'warn', 'error'], + }, + console_log_query: { + label: 'Console log', + description: 'Text of console logs captured', + }, + }, } satisfies Partial>> CORE_FILTER_DEFINITIONS_BY_GROUP.numerical_event_properties = CORE_FILTER_DEFINITIONS_BY_GROUP.event_properties diff --git a/frontend/src/lib/utils/apiHost.ts b/frontend/src/lib/utils/apiHost.ts index bece963761284..75d18c2bf060f 100644 --- a/frontend/src/lib/utils/apiHost.ts +++ b/frontend/src/lib/utils/apiHost.ts @@ -15,6 +15,5 @@ export function liveEventsHostOrigin(): string | null { } else if (appOrigin === 'https://eu.posthog.com') { return 'https://live.eu.posthog.com' } - // TODO(@zach): add dev and local env support - return null + return 'http://localhost:8666' } diff --git a/frontend/src/models/propertyDefinitionsModel.ts b/frontend/src/models/propertyDefinitionsModel.ts index 497a218b3eaaf..1e9a165e02614 100644 --- a/frontend/src/models/propertyDefinitionsModel.ts +++ b/frontend/src/models/propertyDefinitionsModel.ts @@ -55,6 +55,7 @@ export type Option = { label?: string name?: string status?: 'loading' | 'loaded' + allowCustomValues?: boolean values?: PropValue[] } @@ -149,7 +150,11 @@ export const propertyDefinitionsModel = kea([ eventNames?: string[] }) => payload, setOptionsLoading: (key: string) => ({ key }), - setOptions: (key: string, values: PropValue[]) => ({ key, values }), + setOptions: (key: string, values: PropValue[], allowCustomValues: boolean) => ({ + key, + values, + allowCustomValues, + }), // internal fetchAllPendingDefinitions: true, abortAnyRunningQuery: true, @@ -170,11 +175,12 @@ export const propertyDefinitionsModel = kea([ {} as Record, { setOptionsLoading: (state, { key }) => ({ ...state, [key]: { ...state[key], status: 'loading' } }), - setOptions: (state, { key, values }) => ({ + setOptions: (state, { key, values, allowCustomValues }) => ({ ...state, [key]: { values: [...Array.from(new Set(values))], status: 'loaded', + allowCustomValues, }, }), }, @@ -317,6 +323,19 @@ export const propertyDefinitionsModel = kea([ if (!propertyKey || values.currentTeamId === null) { return } + if (propertyKey === 'console_log_level') { + actions.setOptions( + propertyKey, + [ + // id is not used so can be arbitrarily chosen + { id: 0, name: 'info' }, + { id: 1, name: 'warn' }, + { id: 2, name: 'error' }, + ], + false + ) + return + } const start = performance.now() @@ -334,7 +353,7 @@ export const propertyDefinitionsModel = kea([ methodOptions ) breakpoint() - actions.setOptions(propertyKey, propValues) + actions.setOptions(propertyKey, propValues, true) cache.abortController = null await captureTimeToSeeData(teamLogic.values.currentTeamId, { diff --git a/frontend/src/queries/nodes/InsightViz/InsightViz.scss b/frontend/src/queries/nodes/InsightViz/InsightViz.scss index 3d3dde0915dff..67c5fe6145298 100644 --- a/frontend/src/queries/nodes/InsightViz/InsightViz.scss +++ b/frontend/src/queries/nodes/InsightViz/InsightViz.scss @@ -29,7 +29,8 @@ flex-direction: column; .NotebookNode &, - .InsightCard & { + .InsightCard &, + .WebAnalyticsDashboard & { flex: 1; height: 100%; @@ -102,7 +103,8 @@ } .NotebookNode &, - .InsightCard & { + .InsightCard &, + .WebAnalyticsDashboard & { .LineGraph { position: relative; min-height: 100px; @@ -119,7 +121,8 @@ margin: 0.5rem; .NotebookNode &, - .InsightCard & { + .InsightCard &, + .WebAnalyticsDashboard & { min-height: auto; } @@ -149,7 +152,8 @@ min-height: var(--insight-viz-min-height); .NotebookNode &, - .InsightCard & { + .InsightCard &, + .WebAnalyticsDashboard & { min-height: auto; } } diff --git a/frontend/src/queries/nodes/InsightViz/PropertyGroupFilters/AndOrFilterSelect.tsx b/frontend/src/queries/nodes/InsightViz/PropertyGroupFilters/AndOrFilterSelect.tsx index cde744f031014..2d3ef56c69272 100644 --- a/frontend/src/queries/nodes/InsightViz/PropertyGroupFilters/AndOrFilterSelect.tsx +++ b/frontend/src/queries/nodes/InsightViz/PropertyGroupFilters/AndOrFilterSelect.tsx @@ -1,4 +1,4 @@ -import { LemonSelect } from '@posthog/lemon-ui' +import { LemonButtonProps, LemonSelect } from '@posthog/lemon-ui' import { FilterLogicalOperator } from '~/types' @@ -8,6 +8,7 @@ interface AndOrFilterSelectProps { topLevelFilter?: boolean prefix?: React.ReactNode suffix?: [singular: string, plural: string] + disabledReason?: LemonButtonProps['disabledReason'] } export function AndOrFilterSelect({ @@ -16,6 +17,7 @@ export function AndOrFilterSelect({ topLevelFilter, prefix = 'Match', suffix = ['filter in this group', 'filters in this group'], + disabledReason, }: AndOrFilterSelectProps): JSX.Element { return (
@@ -25,6 +27,7 @@ export function AndOrFilterSelect({ size="small" value={value} onChange={(type) => onChange(type as FilterLogicalOperator)} + disabledReason={disabledReason} options={[ { label: 'all', diff --git a/frontend/src/queries/nodes/InsightViz/utils.ts b/frontend/src/queries/nodes/InsightViz/utils.ts index 06d27e0801fd7..77c671d0abcf1 100644 --- a/frontend/src/queries/nodes/InsightViz/utils.ts +++ b/frontend/src/queries/nodes/InsightViz/utils.ts @@ -167,7 +167,7 @@ export function getQueryBasedInsightModel(insight: let query if (insight.query) { query = insight.query - } else if (insight.filters && Object.keys(insight.filters).length > 0) { + } else if (insight.filters && Object.keys(insight.filters).filter((k) => k != 'filter_test_accounts').length > 0) { query = { kind: NodeKind.InsightVizNode, source: filtersToQueryNode(insight.filters) } as InsightVizNode } else { query = null diff --git a/frontend/src/queries/schema.json b/frontend/src/queries/schema.json index 38b26efae384c..d4015b9fa212e 100644 --- a/frontend/src/queries/schema.json +++ b/frontend/src/queries/schema.json @@ -279,7 +279,7 @@ "$ref": "#/definitions/CohortPropertyFilter" }, { - "$ref": "#/definitions/RecordingDurationFilter" + "$ref": "#/definitions/RecordingPropertyFilter" }, { "$ref": "#/definitions/GroupPropertyFilter" @@ -2639,6 +2639,10 @@ "Day": { "type": "integer" }, + "DurationType": { + "enum": ["duration", "active_seconds", "inactive_seconds"], + "type": "string" + }, "ElementPropertyFilter": { "additionalProperties": false, "description": "Sync with plugin-server/src/types.ts", @@ -6964,12 +6968,23 @@ "required": ["k", "t"], "type": "object" }, - "RecordingDurationFilter": { + "RecordingPropertyFilter": { "additionalProperties": false, "properties": { "key": { - "const": "duration", - "type": "string" + "anyOf": [ + { + "$ref": "#/definitions/DurationType" + }, + { + "const": "console_log_level", + "type": "string" + }, + { + "const": "console_log_query", + "type": "string" + } + ] }, "label": { "type": "string" @@ -6982,10 +6997,10 @@ "type": "string" }, "value": { - "type": "number" + "$ref": "#/definitions/PropertyFilterValue" } }, - "required": ["key", "operator", "type", "value"], + "required": ["key", "operator", "type"], "type": "object" }, "RefreshType": { diff --git a/frontend/src/scenes/actions/Action.tsx b/frontend/src/scenes/actions/Action.tsx index 05080a78324e4..5ff2bdc067eb0 100644 --- a/frontend/src/scenes/actions/Action.tsx +++ b/frontend/src/scenes/actions/Action.tsx @@ -11,7 +11,6 @@ import { NodeKind } from '~/queries/schema' import { ActionType } from '~/types' import { ActionEdit } from './ActionEdit' -import { ActionPlugins } from './ActionPlugins' export const scene: SceneExport = { logic: actionLogic, @@ -47,8 +46,6 @@ export function Action({ id }: { id?: ActionType['id'] } = {}): JSX.Element { {id && ( <> - - {isComplete ? (

Matching events

diff --git a/frontend/src/scenes/actions/ActionPlugins.tsx b/frontend/src/scenes/actions/ActionPlugins.tsx deleted file mode 100644 index 56d6807cf35a8..0000000000000 --- a/frontend/src/scenes/actions/ActionPlugins.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { LemonButton, LemonTable } from '@posthog/lemon-ui' -import { useActions, useValues } from 'kea' -import { LemonTableLink } from 'lib/lemon-ui/LemonTable/LemonTableLink' -import { useEffect } from 'react' -import { actionLogic } from 'scenes/actions/actionLogic' -import { PluginImage } from 'scenes/plugins/plugin/PluginImage' -import { urls } from 'scenes/urls' - -import { PipelineNodeTab, PipelineStage } from '~/types' - -export function ActionPlugins(): JSX.Element | null { - const { matchingPluginConfigs } = useValues(actionLogic) - const { loadMatchingPluginConfigs } = useActions(actionLogic) - - useEffect(() => { - loadMatchingPluginConfigs() - }, []) - - if (!matchingPluginConfigs?.length) { - return null - } - - return ( - <> -

Connected data pipelines

- - ( -
- - -
- ), - }, - { - title: '', - width: 0, - render: (_, config) => ( - - Configure - - ), - }, - ]} - /> - - ) -} diff --git a/frontend/src/scenes/activity/explore/EventDetails.tsx b/frontend/src/scenes/activity/explore/EventDetails.tsx index e512838f0020a..8226a7289e266 100644 --- a/frontend/src/scenes/activity/explore/EventDetails.tsx +++ b/frontend/src/scenes/activity/explore/EventDetails.tsx @@ -93,7 +93,7 @@ export function EventDetails({ event, tableProps }: EventDetailsProps): JSX.Elem label: 'Exception', content: (
- +
), }) diff --git a/frontend/src/scenes/activity/live/LiveEventsTable.tsx b/frontend/src/scenes/activity/live/LiveEventsTable.tsx index 9ced6ffbc69a7..a53ff7a1a1a5d 100644 --- a/frontend/src/scenes/activity/live/LiveEventsTable.tsx +++ b/frontend/src/scenes/activity/live/LiveEventsTable.tsx @@ -62,7 +62,7 @@ export function LiveEventsTable(): JSX.Element { - Active users: {stats?.users_on_product ?? '—'} + Users active right now: {stats?.users_on_product ?? '—'}
diff --git a/frontend/src/scenes/activity/live/liveEventsTableLogic.tsx b/frontend/src/scenes/activity/live/liveEventsTableLogic.tsx index efc76ece220f3..a3fe1e9651635 100644 --- a/frontend/src/scenes/activity/live/liveEventsTableLogic.tsx +++ b/frontend/src/scenes/activity/live/liveEventsTableLogic.tsx @@ -18,7 +18,6 @@ export const liveEventsTableLogic = kea([ addEvents: (events) => ({ events }), clearEvents: true, setFilters: (filters) => ({ filters }), - updateEventsSource: (source) => ({ source }), updateEventsConnection: true, pauseStream: true, resumeStream: true, @@ -54,12 +53,6 @@ export const liveEventsTableLogic = kea([ setClientSideFilters: (_, { clientSideFilters }) => clientSideFilters, }, ], - eventsSource: [ - null as EventSource | null, - { - updateEventsSource: (_, { source }) => source, - }, - ], streamPaused: [ false, { @@ -110,8 +103,8 @@ export const liveEventsTableLogic = kea([ actions.updateEventsConnection() }, updateEventsConnection: async () => { - if (values.eventsSource) { - values.eventsSource.close() + if (cache.eventsSource) { + cache.eventsSource.close() } if (values.streamPaused) { @@ -124,14 +117,13 @@ export const liveEventsTableLogic = kea([ const { eventType } = values.filters const url = new URL(`${liveEventsHostOrigin()}/events`) - url.searchParams.append('teamId', values.currentTeam.id.toString()) if (eventType) { url.searchParams.append('eventType', eventType) } const source = new window.EventSourcePolyfill(url.toString(), { headers: { - Authorization: `Bearer ${values.currentTeam?.live_events_token}`, + Authorization: `Bearer ${values.currentTeam.live_events_token}`, }, }) @@ -158,11 +150,11 @@ export const liveEventsTableLogic = kea([ } } - actions.updateEventsSource(source) + cache.eventsSource = source }, pauseStream: () => { - if (values.eventsSource) { - values.eventsSource.close() + if (cache.eventsSource) { + cache.eventsSource.close() } }, resumeStream: () => { @@ -174,14 +166,11 @@ export const liveEventsTableLogic = kea([ return } - const response = await fetch( - `${liveEventsHostOrigin()}/stats?teamId=${values.currentTeam.id.toString()}`, - { - headers: { - Authorization: `Bearer ${values.currentTeam?.live_events_token}`, - }, - } - ) + const response = await fetch(`${liveEventsHostOrigin()}/stats`, { + headers: { + Authorization: `Bearer ${values.currentTeam.live_events_token}`, + }, + }) const data = await response.json() actions.setStats(data) } catch (error) { @@ -189,21 +178,19 @@ export const liveEventsTableLogic = kea([ } }, })), - events(({ actions, values }) => ({ + events(({ actions, cache }) => ({ afterMount: () => { - if (!liveEventsHostOrigin()) { - return - } - actions.updateEventsConnection() - const interval = setInterval(() => { + cache.statsInterval = setInterval(() => { actions.pollStats() }, 1500) - return () => { - if (values.eventsSource) { - values.eventsSource.close() - } - clearInterval(interval) + }, + beforeUnmount: () => { + if (cache.eventsSource) { + cache.eventsSource.close() + } + if (cache.statsInterval) { + clearInterval(cache.statsInterval) } }, })), diff --git a/frontend/src/scenes/appScenes.ts b/frontend/src/scenes/appScenes.ts index 07da32a054160..01903f190d5c2 100644 --- a/frontend/src/scenes/appScenes.ts +++ b/frontend/src/scenes/appScenes.ts @@ -37,6 +37,7 @@ export const appScenes: Record any> = { [Scene.FeatureFlag]: () => import('./feature-flags/FeatureFlag'), [Scene.EarlyAccessFeatures]: () => import('./early-access-features/EarlyAccessFeatures'), [Scene.EarlyAccessFeature]: () => import('./early-access-features/EarlyAccessFeature'), + [Scene.ErrorTracking]: () => import('./error-tracking/ErrorTrackingScene'), [Scene.Surveys]: () => import('./surveys/Surveys'), [Scene.Survey]: () => import('./surveys/Survey'), [Scene.SurveyTemplates]: () => import('./surveys/SurveyTemplates'), diff --git a/frontend/src/scenes/authentication/InviteSignup.tsx b/frontend/src/scenes/authentication/InviteSignup.tsx index af96ad730bd15..32a612d36e93b 100644 --- a/frontend/src/scenes/authentication/InviteSignup.tsx +++ b/frontend/src/scenes/authentication/InviteSignup.tsx @@ -285,7 +285,7 @@ function UnauthenticatedAcceptInvite({ invite }: { invite: PrevalidatedInvite }) caption={`Remember to log in with ${invite?.target_email}`} captionLocation="bottom" topDivider - redirectQueryParams={invite ? { invite_id: invite.id } : undefined} + extraQueryParams={invite ? { invite_id: invite.id } : undefined} /> ) diff --git a/frontend/src/scenes/billing/BillingProductAddon.tsx b/frontend/src/scenes/billing/BillingProductAddon.tsx index b39fd32f0d51d..57a9c2edb2803 100644 --- a/frontend/src/scenes/billing/BillingProductAddon.tsx +++ b/frontend/src/scenes/billing/BillingProductAddon.tsx @@ -97,7 +97,7 @@ export const BillingProductAddon = ({ addon }: { addon: BillingProductV2AddonTyp {is_enhanced_persons_og_customer && (

([ }, ], })), - events(({ actions, cache, props }) => ({ + events(({ actions, cache, props, values }) => ({ afterMount: () => { if (props.id) { if (props.dashboard) { @@ -843,7 +844,7 @@ export const dashboardLogic = kea([ actions.loadDashboardSuccess(props.dashboard) } else { actions.loadDashboard({ - refresh: 'force_cache', + refresh: values.featureFlags[FEATURE_FLAGS.HOGQL_DASHBOARD_ASYNC] ? 'async' : 'force_cache', action: 'initial_load', }) } @@ -966,7 +967,7 @@ export const dashboardLogic = kea([ const insightsToRefresh = values .sortTilesByLayout(tiles || values.insightTiles || []) .filter((t) => { - if (!initialLoad || !t.last_refresh) { + if (!initialLoad || !t.last_refresh || !!t.insight?.query_status) { return true } @@ -1016,7 +1017,13 @@ export const dashboardLogic = kea([ const queryId = `${dashboardQueryId}::${uuid()}` const queryStartTime = performance.now() const apiUrl = `api/projects/${values.currentTeamId}/insights/${insight.id}/?${toParams({ - refresh: hardRefreshWithoutCache ? 'force_blocking' : 'blocking', + refresh: values.featureFlags[FEATURE_FLAGS.HOGQL_DASHBOARD_ASYNC] + ? hardRefreshWithoutCache + ? 'force_async' + : 'async' + : hardRefreshWithoutCache + ? 'force_blocking' + : 'blocking', from_dashboard: dashboardId, // needed to load insight in correct context client_query_id: queryId, session_id: currentSessionId(), @@ -1056,29 +1063,22 @@ export const dashboardLogic = kea([ } if (refreshedInsight.query_status) { - pollForResults(refreshedInsight.query_status.id, false, methodOptions) - .then(async () => { - const apiUrl = `api/projects/${values.currentTeamId}/insights/${insight.id}/?${toParams( - { - refresh: 'async', - from_dashboard: dashboardId, // needed to load insight in correct context - client_query_id: queryId, - session_id: currentSessionId(), - } - )}` - // TODO: We get the insight again here to get everything in the right format (e.g. because of result vs results) - const refreshedInsightResponse: Response = await api.getResponse(apiUrl, methodOptions) - const refreshedInsight: InsightModel = await getJSONOrNull(refreshedInsightResponse) - dashboardsModel.actions.updateDashboardInsight( - refreshedInsight, - [], - props.id ? [props.id] : undefined - ) - actions.setRefreshStatus(insight.short_id) - }) - .catch(() => { - actions.setRefreshError(insight.short_id) - }) + await pollForResults(refreshedInsight.query_status.id, false, methodOptions) + const apiUrl = `api/projects/${values.currentTeamId}/insights/${insight.id}/?${toParams({ + refresh: 'async', + from_dashboard: dashboardId, // needed to load insight in correct context + client_query_id: queryId, + session_id: currentSessionId(), + })}` + // TODO: We get the insight again here to get everything in the right format (e.g. because of result vs results) + const polledInsightResponse: Response = await api.getResponse(apiUrl, methodOptions) + const polledInsight: InsightModel = await getJSONOrNull(polledInsightResponse) + dashboardsModel.actions.updateDashboardInsight( + polledInsight, + [], + props.id ? [props.id] : undefined + ) + actions.setRefreshStatus(insight.short_id) } else { actions.setRefreshStatus(insight.short_id) } @@ -1184,6 +1184,7 @@ export const dashboardLogic = kea([ // Initial load of actual data for dashboard items after general dashboard is fetched if ( + !values.featureFlags[FEATURE_FLAGS.HOGQL_DASHBOARD_ASYNC] && // with async we straight up want to loop through all items values.oldestRefreshed && values.oldestRefreshed.isBefore(now().subtract(AUTO_REFRESH_DASHBOARD_THRESHOLD_HOURS, 'hours')) && !process.env.STORYBOOK // allow mocking of date in storybook without triggering refresh @@ -1191,11 +1192,12 @@ export const dashboardLogic = kea([ actions.refreshAllDashboardItems({ action: 'refresh', initialLoad, dashboardQueryId }) allLoaded = false } else { - const tilesWithNoResults = values.tiles?.filter((t) => !!t.insight && !t.insight.result) || [] + const tilesWithNoOrQueuedResults = + values.tiles?.filter((t) => !!t.insight && (!t.insight.result || !!t.insight.query_status)) || [] - if (tilesWithNoResults.length) { + if (tilesWithNoOrQueuedResults.length) { actions.refreshAllDashboardItems({ - tiles: tilesWithNoResults, + tiles: tilesWithNoOrQueuedResults, action: 'load_missing', initialLoad, dashboardQueryId, diff --git a/frontend/src/scenes/data-warehouse/ViewLinkModal.tsx b/frontend/src/scenes/data-warehouse/ViewLinkModal.tsx index 5a01206bd52cf..df06fc5df3054 100644 --- a/frontend/src/scenes/data-warehouse/ViewLinkModal.tsx +++ b/frontend/src/scenes/data-warehouse/ViewLinkModal.tsx @@ -184,7 +184,7 @@ export function ViewLinkForm(): JSX.Element {

- + {sqlCodeSnippet}
diff --git a/frontend/src/scenes/data-warehouse/settings/DataWarehouseSourcesTable.tsx b/frontend/src/scenes/data-warehouse/settings/DataWarehouseSourcesTable.tsx index d4e9082c55c49..2a0dd8b31314b 100644 --- a/frontend/src/scenes/data-warehouse/settings/DataWarehouseSourcesTable.tsx +++ b/frontend/src/scenes/data-warehouse/settings/DataWarehouseSourcesTable.tsx @@ -1,5 +1,15 @@ import { TZLabel } from '@posthog/apps-common' -import { LemonButton, LemonDialog, LemonSwitch, LemonTable, LemonTag, Link, Spinner, Tooltip } from '@posthog/lemon-ui' +import { + LemonButton, + LemonDialog, + LemonSelect, + LemonSwitch, + LemonTable, + LemonTag, + Link, + Spinner, + Tooltip, +} from '@posthog/lemon-ui' import { useActions, useValues } from 'kea' import { router } from 'kea-router' import { ProductIntroduction } from 'lib/components/ProductIntroduction/ProductIntroduction' @@ -14,10 +24,10 @@ import { urls } from 'scenes/urls' import { DataTableNode, NodeKind } from '~/queries/schema' import { + DataWarehouseSyncInterval, ExternalDataSourceSchema, ExternalDataSourceType, ExternalDataStripeSource, - PipelineInterval, ProductKey, } from '~/types' @@ -33,7 +43,7 @@ const StatusTagSetting = { export function DataWarehouseSourcesTable(): JSX.Element { const { dataWarehouseSources, dataWarehouseSourcesLoading, sourceReloadingById } = useValues(dataWarehouseSettingsLogic) - const { deleteSource, reloadSource } = useActions(dataWarehouseSettingsLogic) + const { deleteSource, reloadSource, updateSource } = useActions(dataWarehouseSettingsLogic) const renderExpandable = (source: ExternalDataStripeSource): JSX.Element => { return ( @@ -90,8 +100,20 @@ export function DataWarehouseSourcesTable(): JSX.Element { { title: 'Sync Frequency', key: 'frequency', - render: function RenderFrequency() { - return 'day' as PipelineInterval + render: function RenderFrequency(_, source) { + return ( + + updateSource({ ...source, sync_frequency: value as DataWarehouseSyncInterval }) + } + options={[ + { value: 'day' as DataWarehouseSyncInterval, label: 'Daily' }, + { value: 'week' as DataWarehouseSyncInterval, label: 'Weekly' }, + { value: 'month' as DataWarehouseSyncInterval, label: 'Monthly' }, + ]} + /> + ) }, }, { diff --git a/frontend/src/scenes/data-warehouse/settings/dataWarehouseSettingsLogic.ts b/frontend/src/scenes/data-warehouse/settings/dataWarehouseSettingsLogic.ts index a9cd46d0360da..19795ffe3c37a 100644 --- a/frontend/src/scenes/data-warehouse/settings/dataWarehouseSettingsLogic.ts +++ b/frontend/src/scenes/data-warehouse/settings/dataWarehouseSettingsLogic.ts @@ -25,7 +25,7 @@ export const dataWarehouseSettingsLogic = kea([ updateSchema: (schema: ExternalDataSourceSchema) => ({ schema }), abortAnyRunningQuery: true, }), - loaders(({ cache, actions }) => ({ + loaders(({ cache, actions, values }) => ({ dataWarehouseSources: [ null as PaginatedResponse | null, { @@ -45,6 +45,15 @@ export const dataWarehouseSettingsLogic = kea([ return res }, + updateSource: async (source: ExternalDataStripeSource) => { + const updatedSource = await api.externalDataSources.update(source.id, source) + return { + ...values.dataWarehouseSources, + results: + values.dataWarehouseSources?.results.map((s) => (s.id === updatedSource.id ? source : s)) || + [], + } + }, }, ], })), diff --git a/frontend/src/scenes/heatmaps/HeatmapsBrowser.tsx b/frontend/src/scenes/heatmaps/HeatmapsBrowser.tsx index c1d395eb7ff8c..af088a550cacd 100644 --- a/frontend/src/scenes/heatmaps/HeatmapsBrowser.tsx +++ b/frontend/src/scenes/heatmaps/HeatmapsBrowser.tsx @@ -1,4 +1,4 @@ -import { IconCollapse } from '@posthog/icons' +import { IconCollapse, IconGear } from '@posthog/icons' import { LemonBanner, LemonButton, LemonInputSelect, LemonSkeleton, Spinner, Tooltip } from '@posthog/lemon-ui' import { BindLogic, useActions, useValues } from 'kea' import { AuthorizedUrlList } from 'lib/components/AuthorizedUrlList/AuthorizedUrlList' @@ -10,6 +10,9 @@ import { DetectiveHog } from 'lib/components/hedgehogs' import { useResizeObserver } from 'lib/hooks/useResizeObserver' import { IconChevronRight, IconOpenInNew } from 'lib/lemon-ui/icons' import React, { useEffect, useRef } from 'react' +import { teamLogic } from 'scenes/teamLogic' + +import { sidePanelSettingsLogic } from '~/layout/navigation-3000/sidepanel/panels/sidePanelSettingsLogic' import { heatmapsBrowserLogic } from './heatmapsBrowserLogic' @@ -260,6 +263,28 @@ function EmbeddedHeatmapBrowser({ ) : null } +function Warnings(): JSX.Element | null { + const { currentTeam } = useValues(teamLogic) + const heatmapsEnabled = currentTeam?.heatmaps_opt_in + + const { openSettingsPanel } = useActions(sidePanelSettingsLogic) + + return !heatmapsEnabled ? ( + , + onClick: () => openSettingsPanel({ settingId: 'heatmaps' }), + children: 'Configure', + }} + dismissKey="heatmaps-might-be-disabled-warning" + > + You aren't collecting heatmaps data. Enable heatmaps in your project. + + ) : null +} + export function HeatmapsBrowser(): JSX.Element { const iframeRef = useRef(null) @@ -271,7 +296,8 @@ export function HeatmapsBrowser(): JSX.Element { return ( -
+
+
diff --git a/frontend/src/scenes/onboarding/sdks/sdk-install-instructions/nuxt.tsx b/frontend/src/scenes/onboarding/sdks/sdk-install-instructions/nuxt.tsx index e57a46e534d61..baa4947ad4950 100644 --- a/frontend/src/scenes/onboarding/sdks/sdk-install-instructions/nuxt.tsx +++ b/frontend/src/scenes/onboarding/sdks/sdk-install-instructions/nuxt.tsx @@ -35,7 +35,7 @@ import posthog from 'posthog-js' export default defineNuxtPlugin(nuxtApp => { const runtimeConfig = useRuntimeConfig(); const posthogClient = posthog.init(runtimeConfig.public.posthogPublicKey, { - api_host: runtimeConfig.public.posthogHost', + api_host: runtimeConfig.public.posthogHost, ${ isPersonProfilesDisabled ? `` diff --git a/frontend/src/scenes/pipeline/Destinations.tsx b/frontend/src/scenes/pipeline/Destinations.tsx index 61af57f3061c5..500111bdc5d1b 100644 --- a/frontend/src/scenes/pipeline/Destinations.tsx +++ b/frontend/src/scenes/pipeline/Destinations.tsx @@ -57,10 +57,14 @@ export function DestinationsTable({ inOverview = false }: { inOverview?: boolean title: 'App', width: 0, render: function RenderAppInfo(_, destination) { - if (destination.backend === 'plugin') { - return + switch (destination.backend) { + case 'plugin': + return + case 'batch_export': + return + default: + return null } - return }, }, { diff --git a/frontend/src/scenes/pipeline/PipelineNodeConfiguration.tsx b/frontend/src/scenes/pipeline/PipelineNodeConfiguration.tsx index 61d09e36015c4..926469a85ffd6 100644 --- a/frontend/src/scenes/pipeline/PipelineNodeConfiguration.tsx +++ b/frontend/src/scenes/pipeline/PipelineNodeConfiguration.tsx @@ -1,6 +1,7 @@ import { useValues } from 'kea' import { NotFound } from 'lib/components/NotFound' +import { PipelineHogFunctionConfiguration } from './hogfunctions/PipelineHogFunctionConfiguration' import { PipelineBatchExportConfiguration } from './PipelineBatchExportConfiguration' import { pipelineNodeLogic } from './pipelineNodeLogic' import { PipelinePluginConfiguration } from './PipelinePluginConfiguration' @@ -15,7 +16,9 @@ export function PipelineNodeConfiguration(): JSX.Element { return (
- {node.backend === PipelineBackend.Plugin ? ( + {node.backend === PipelineBackend.HogFunction ? ( + + ) : node.backend === PipelineBackend.Plugin ? ( ) : ( diff --git a/frontend/src/scenes/pipeline/PipelineNodeNew.tsx b/frontend/src/scenes/pipeline/PipelineNodeNew.tsx index 30504da3fe51d..76eea7518a6b0 100644 --- a/frontend/src/scenes/pipeline/PipelineNodeNew.tsx +++ b/frontend/src/scenes/pipeline/PipelineNodeNew.tsx @@ -1,17 +1,20 @@ import { IconPlusSmall } from '@posthog/icons' import { useValues } from 'kea' +import { combineUrl, router } from 'kea-router' import { NotFound } from 'lib/components/NotFound' import { PayGateMini } from 'lib/components/PayGateMini/PayGateMini' +import { useFeatureFlag } from 'lib/hooks/useFeatureFlag' import { LemonButton } from 'lib/lemon-ui/LemonButton' import { LemonTable } from 'lib/lemon-ui/LemonTable' import { LemonTableLink } from 'lib/lemon-ui/LemonTable/LemonTableLink' import { SceneExport } from 'scenes/sceneTypes' import { urls } from 'scenes/urls' -import { AvailableFeature, BatchExportService, PipelineStage, PluginType } from '~/types' +import { AvailableFeature, BatchExportService, HogFunctionTemplateType, PipelineStage, PluginType } from '~/types' import { pipelineDestinationsLogic } from './destinationsLogic' import { frontendAppsLogic } from './frontendAppsLogic' +import { PipelineHogFunctionConfiguration } from './hogfunctions/PipelineHogFunctionConfiguration' import { PipelineBatchExportConfiguration } from './PipelineBatchExportConfiguration' import { PIPELINE_TAB_TO_NODE_STAGE } from './PipelineNode' import { pipelineNodeNewLogic, PipelineNodeNewLogicProps } from './pipelineNodeNewLogic' @@ -21,21 +24,20 @@ import { PipelineBackend } from './types' import { getBatchExportUrl, RenderApp, RenderBatchExportIcon } from './utils' const paramsToProps = ({ - params: { stage, pluginIdOrBatchExportDestination }, + params: { stage, id }, }: { - params: { stage?: string; pluginIdOrBatchExportDestination?: string } + params: { stage?: string; id?: string } }): PipelineNodeNewLogicProps => { - const numericId = - pluginIdOrBatchExportDestination && /^\d+$/.test(pluginIdOrBatchExportDestination) - ? parseInt(pluginIdOrBatchExportDestination) - : undefined + const numericId = id && /^\d+$/.test(id) ? parseInt(id) : undefined const pluginId = numericId && !isNaN(numericId) ? numericId : null - const batchExportDestination = pluginId ? null : pluginIdOrBatchExportDestination ?? null + const hogFunctionId = pluginId ? null : id?.startsWith('hog-') ? id.slice(4) : null + const batchExportDestination = hogFunctionId ? null : id ?? null return { stage: PIPELINE_TAB_TO_NODE_STAGE[stage + 's'] || null, // pipeline tab has stage plural here we have singular - pluginId: pluginId, - batchExportDestination: batchExportDestination, + pluginId, + batchExportDestination, + hogFunctionId, } } @@ -45,32 +47,22 @@ export const scene: SceneExport = { paramsToProps, } -type PluginEntry = { - backend: PipelineBackend.Plugin - id: number +type TableEntry = { + backend: PipelineBackend + id: string | number name: string description: string - plugin: PluginType url?: string + icon: JSX.Element } -type BatchExportEntry = { - backend: PipelineBackend.BatchExport - id: BatchExportService['type'] - name: string - description: string - url: string -} - -type TableEntry = PluginEntry | BatchExportEntry - function convertPluginToTableEntry(plugin: PluginType): TableEntry { return { backend: PipelineBackend.Plugin, id: plugin.id, name: plugin.name, description: plugin.description || '', - plugin: plugin, + icon: , // TODO: ideally we'd link to docs instead of GitHub repo, so it can open in panel // Same for transformations and destinations tables url: plugin.url, @@ -80,17 +72,26 @@ function convertPluginToTableEntry(plugin: PluginType): TableEntry { function convertBatchExportToTableEntry(service: BatchExportService['type']): TableEntry { return { backend: PipelineBackend.BatchExport, - id: service, + id: service as string, name: service, description: `${service} batch export`, + icon: , url: getBatchExportUrl(service), } } -export function PipelineNodeNew( - params: { stage?: string; pluginIdOrBatchExportDestination?: string } = {} -): JSX.Element { - const { stage, pluginId, batchExportDestination } = paramsToProps({ params }) +function convertHogFunctionToTableEntry(hogFunction: HogFunctionTemplateType): TableEntry { + return { + backend: PipelineBackend.HogFunction, + id: `hog-${hogFunction.id}`, // TODO: This weird identifier thing isn't great + name: hogFunction.name, + description: hogFunction.description, + icon: 🦔, + } +} + +export function PipelineNodeNew(params: { stage?: string; id?: string } = {}): JSX.Element { + const { stage, pluginId, batchExportDestination, hogFunctionId } = paramsToProps({ params }) if (!stage) { return @@ -103,6 +104,7 @@ export function PipelineNodeNew( } return res } + if (batchExportDestination) { if (stage !== PipelineStage.Destination) { return @@ -114,6 +116,14 @@ export function PipelineNodeNew( ) } + if (hogFunctionId) { + const res = + if (stage === PipelineStage.Destination) { + return {res} + } + return res + } + if (stage === PipelineStage.Transformation) { return } else if (stage === PipelineStage.Destination) { @@ -135,11 +145,15 @@ function TransformationOptionsTable(): JSX.Element { } function DestinationOptionsTable(): JSX.Element { + const hogFunctionsEnabled = !!useFeatureFlag('HOG_FUNCTIONS') const { batchExportServiceNames } = useValues(pipelineNodeNewLogic) - const { plugins, loading } = useValues(pipelineDestinationsLogic) + const { plugins, loading, hogFunctionTemplates } = useValues(pipelineDestinationsLogic) const pluginTargets = Object.values(plugins).map(convertPluginToTableEntry) const batchExportTargets = Object.values(batchExportServiceNames).map(convertBatchExportToTableEntry) - const targets = [...batchExportTargets, ...pluginTargets] + const hogFunctionTargets = hogFunctionsEnabled + ? Object.values(hogFunctionTemplates).map(convertHogFunctionToTableEntry) + : [] + const targets = [...batchExportTargets, ...pluginTargets, ...hogFunctionTargets] return } @@ -158,6 +172,7 @@ function NodeOptionsTable({ targets: TableEntry[] loading: boolean }): JSX.Element { + const { hashParams } = useValues(router) return ( <> - } - return + return target.icon }, }, { @@ -198,7 +210,8 @@ function NodeOptionsTable({ type="primary" data-attr={`new-${stage}-${target.id}`} icon={} - to={urls.pipelineNodeNew(stage, target.id)} + // Preserve hash params to pass config in + to={combineUrl(urls.pipelineNodeNew(stage, target.id), {}, hashParams).url} > Create diff --git a/frontend/src/scenes/pipeline/destinationsLogic.tsx b/frontend/src/scenes/pipeline/destinationsLogic.tsx index d50e6b5de5091..05d4e2ff1d1e6 100644 --- a/frontend/src/scenes/pipeline/destinationsLogic.tsx +++ b/frontend/src/scenes/pipeline/destinationsLogic.tsx @@ -8,6 +8,8 @@ import { userLogic } from 'scenes/userLogic' import { BatchExportConfiguration, + HogFunctionTemplateType, + HogFunctionType, PipelineStage, PluginConfigTypeNew, PluginConfigWithPluginInfoNew, @@ -16,6 +18,7 @@ import { } from '~/types' import type { pipelineDestinationsLogicType } from './destinationsLogicType' +import { HOG_FUNCTION_TEMPLATES } from './hogfunctions/templates/hog-templates' import { pipelineAccessLogic } from './pipelineAccessLogic' import { BatchExportDestination, convertToPipelineNode, Destination, PipelineBackend } from './types' import { captureBatchExportEvent, capturePluginEvent, loadPluginsFromUrl } from './utils' @@ -116,28 +119,68 @@ export const pipelineDestinationsLogic = kea([ }, }, ], + + hogFunctionTemplates: [ + {} as Record, + { + loadHogFunctionTemplates: async () => { + return HOG_FUNCTION_TEMPLATES.reduce((acc, template) => { + acc[template.id] = template + return acc + }, {} as Record) + }, + }, + ], + hogFunctions: [ + [] as HogFunctionType[], + { + loadHogFunctions: async () => { + // TODO: Support pagination? + return (await api.hogFunctions.list()).results + }, + }, + ], })), selectors({ loading: [ - (s) => [s.pluginsLoading, s.pluginConfigsLoading, s.batchExportConfigsLoading], - (pluginsLoading, pluginConfigsLoading, batchExportConfigsLoading) => - pluginsLoading || pluginConfigsLoading || batchExportConfigsLoading, + (s) => [ + s.pluginsLoading, + s.pluginConfigsLoading, + s.batchExportConfigsLoading, + s.hogFunctionTemplatesLoading, + s.hogFunctionsLoading, + ], + ( + pluginsLoading, + pluginConfigsLoading, + batchExportConfigsLoading, + hogFunctionTemplatesLoading, + hogFunctionsLoading + ) => + pluginsLoading || + pluginConfigsLoading || + batchExportConfigsLoading || + hogFunctionTemplatesLoading || + hogFunctionsLoading, ], destinations: [ - (s) => [s.pluginConfigs, s.plugins, s.batchExportConfigs, s.user], - (pluginConfigs, plugins, batchExportConfigs, user): Destination[] => { + (s) => [s.pluginConfigs, s.plugins, s.batchExportConfigs, s.hogFunctions, s.user], + (pluginConfigs, plugins, batchExportConfigs, hogFunctions, user): Destination[] => { // Migrations are shown only in impersonation mode, for us to be able to trigger them. const rawBatchExports = Object.values(batchExportConfigs).filter( (config) => config.destination.type !== 'HTTP' || user?.is_impersonated ) - const rawDestinations: (PluginConfigWithPluginInfoNew | BatchExportConfiguration)[] = Object.values( - pluginConfigs - ) - .map((pluginConfig) => ({ - ...pluginConfig, - plugin_info: plugins[pluginConfig.plugin] || null, - })) - .concat(rawBatchExports) + + const rawDestinations: (PluginConfigWithPluginInfoNew | BatchExportConfiguration | HogFunctionType)[] = + Object.values(pluginConfigs) + .map( + (pluginConfig) => ({ + ...pluginConfig, + plugin_info: plugins[pluginConfig.plugin] || null, + }) + ) + .concat(rawBatchExports) + .concat(hogFunctions) const convertedDestinations = rawDestinations.map((d) => convertToPipelineNode(d, PipelineStage.Destination) ) @@ -183,5 +226,7 @@ export const pipelineDestinationsLogic = kea([ actions.loadPlugins() actions.loadPluginConfigs() actions.loadBatchExports() + actions.loadHogFunctionTemplates() + actions.loadHogFunctions() }), ]) diff --git a/frontend/src/scenes/pipeline/pipelineNodeLogic.tsx b/frontend/src/scenes/pipeline/pipelineNodeLogic.tsx index 38d5acba5fcd3..f9a4d7d66b824 100644 --- a/frontend/src/scenes/pipeline/pipelineNodeLogic.tsx +++ b/frontend/src/scenes/pipeline/pipelineNodeLogic.tsx @@ -24,7 +24,11 @@ type BatchExportNodeId = { backend: PipelineBackend.BatchExport id: string } -export type PipelineNodeLimitedType = PluginNodeId | BatchExportNodeId +type HogFunctionNodeId = { + backend: PipelineBackend.HogFunction + id: string +} +export type PipelineNodeLimitedType = PluginNodeId | BatchExportNodeId | HogFunctionNodeId export const pipelineNodeLogic = kea([ props({} as PipelineNodeLogicProps), @@ -61,18 +65,23 @@ export const pipelineNodeLogic = kea([ }, ], ], + + nodeBackend: [ + (s) => [s.node], + (node): PipelineBackend => { + return node.backend + }, + ], node: [ (_, p) => [p.id], (id): PipelineNodeLimitedType => { return typeof id === 'string' - ? { backend: PipelineBackend.BatchExport, id: id } - : { backend: PipelineBackend.Plugin, id: id } + ? id.indexOf('hog-') === 0 + ? { backend: PipelineBackend.HogFunction, id: `${id}`.replace('hog-', '') } + : { backend: PipelineBackend.BatchExport, id } + : { backend: PipelineBackend.Plugin, id } }, ], - nodeBackend: [ - (_, p) => [p.id], - (id): PipelineBackend => (typeof id === 'string' ? PipelineBackend.BatchExport : PipelineBackend.Plugin), - ], tabs: [ (s) => [s.nodeBackend], (nodeBackend) => { diff --git a/frontend/src/scenes/pipeline/pipelineNodeNewLogic.tsx b/frontend/src/scenes/pipeline/pipelineNodeNewLogic.tsx index 395055b913a9b..81e45ff15d394 100644 --- a/frontend/src/scenes/pipeline/pipelineNodeNewLogic.tsx +++ b/frontend/src/scenes/pipeline/pipelineNodeNewLogic.tsx @@ -18,6 +18,7 @@ export interface PipelineNodeNewLogicProps { stage: PipelineStage | null pluginId: number | null batchExportDestination: string | null + hogFunctionId: string | null } export const pipelineNodeNewLogic = kea([ @@ -25,12 +26,7 @@ export const pipelineNodeNewLogic = kea([ connect({ values: [userLogic, ['user']], }), - path((pluginIdOrBatchExportDestination) => [ - 'scenes', - 'pipeline', - 'pipelineNodeNewLogic', - pluginIdOrBatchExportDestination, - ]), + path((id) => ['scenes', 'pipeline', 'pipelineNodeNewLogic', id]), actions({ createNewButtonPressed: (stage: PipelineStage, id: number | BatchExportService['type']) => ({ stage, id }), }), diff --git a/frontend/src/scenes/pipeline/types.ts b/frontend/src/scenes/pipeline/types.ts index dc6ac93442df9..f958ebb887ca6 100644 --- a/frontend/src/scenes/pipeline/types.ts +++ b/frontend/src/scenes/pipeline/types.ts @@ -1,6 +1,7 @@ import { BatchExportConfiguration, BatchExportService, + HogFunctionType, PipelineStage, PluginConfigWithPluginInfoNew, PluginType, @@ -9,6 +10,7 @@ import { export enum PipelineBackend { BatchExport = 'batch_export', Plugin = 'plugin', + HogFunction = 'hog_function', } // Base - we're taking a discriminated union approach here, so that TypeScript can discern types for free @@ -39,6 +41,11 @@ export interface BatchExportBasedNode extends PipelineNodeBase { interval: BatchExportConfiguration['interval'] } +export interface HogFunctionBasedNode extends PipelineNodeBase { + backend: PipelineBackend.HogFunction + id: string +} + // Stage: Transformations export interface Transformation extends PluginBasedNode { @@ -55,7 +62,11 @@ export interface WebhookDestination extends PluginBasedNode { export interface BatchExportDestination extends BatchExportBasedNode { stage: PipelineStage.Destination } -export type Destination = BatchExportDestination | WebhookDestination +export interface FunctionDestination extends HogFunctionBasedNode { + stage: PipelineStage.Destination + interval: 'realtime' +} +export type Destination = BatchExportDestination | WebhookDestination | FunctionDestination export interface DataImportApp extends PluginBasedNode { stage: PipelineStage.DataImport @@ -84,7 +95,7 @@ function isPluginConfig( } export function convertToPipelineNode( - candidate: PluginConfigWithPluginInfoNew | BatchExportConfiguration, + candidate: PluginConfigWithPluginInfoNew | BatchExportConfiguration | HogFunctionType, stage: S ): S extends PipelineStage.Transformation ? Transformation @@ -98,7 +109,20 @@ export function convertToPipelineNode( ? ImportApp : never { let node: PipelineNode - if (isPluginConfig(candidate)) { + // check if type is a hog function + if ('hog' in candidate) { + node = { + stage: stage as PipelineStage.Destination, + backend: PipelineBackend.HogFunction, + interval: 'realtime', + id: `hog-${candidate.id}`, + name: candidate.name, + description: candidate.description, + enabled: candidate.enabled, + created_at: candidate.created_at, + updated_at: candidate.created_at, + } + } else if (isPluginConfig(candidate)) { const almostNode: Omit< Transformation | WebhookDestination | SiteApp | ImportApp | DataImportApp, 'frequency' | 'order' diff --git a/frontend/src/scenes/sceneTypes.ts b/frontend/src/scenes/sceneTypes.ts index c80897e8dd192..1dcb3f8af312b 100644 --- a/frontend/src/scenes/sceneTypes.ts +++ b/frontend/src/scenes/sceneTypes.ts @@ -9,6 +9,7 @@ export enum Scene { Error404 = '404', ErrorNetwork = '4xx', ErrorProjectUnavailable = 'ProjectUnavailable', + ErrorTracking = 'ErrorTracking', Dashboards = 'Dashboards', Dashboard = 'Dashboard', Insight = 'Insight', diff --git a/frontend/src/scenes/scenes.ts b/frontend/src/scenes/scenes.ts index 94983524158f6..f4ef644d8665c 100644 --- a/frontend/src/scenes/scenes.ts +++ b/frontend/src/scenes/scenes.ts @@ -53,6 +53,10 @@ export const sceneConfigurations: Record = { activityScope: ActivityScope.DASHBOARD, defaultDocsPath: '/docs/product-analytics/dashboards', }, + [Scene.ErrorTracking]: { + projectBased: true, + name: 'Error tracking', + }, [Scene.Insight]: { projectBased: true, name: 'Insights', @@ -408,7 +412,6 @@ export const sceneConfigurations: Record = { [Scene.Heatmaps]: { projectBased: true, name: 'Heatmaps', - hideProjectNotice: true, }, } @@ -529,7 +532,7 @@ export const routes: Record = { [urls.persons()]: Scene.PersonsManagement, [urls.pipelineNodeDataWarehouseNew()]: Scene.pipelineNodeDataWarehouseNew, [urls.pipelineNodeNew(':stage')]: Scene.PipelineNodeNew, - [urls.pipelineNodeNew(':stage', ':pluginIdOrBatchExportDestination')]: Scene.PipelineNodeNew, + [urls.pipelineNodeNew(':stage', ':id')]: Scene.PipelineNodeNew, [urls.pipeline(':tab')]: Scene.Pipeline, [urls.pipelineNode(':stage', ':id', ':nodeTab')]: Scene.PipelineNode, [urls.groups(':groupTypeIndex')]: Scene.PersonsManagement, @@ -541,6 +544,7 @@ export const routes: Record = { [urls.experiment(':id')]: Scene.Experiment, [urls.earlyAccessFeatures()]: Scene.EarlyAccessFeatures, [urls.earlyAccessFeature(':id')]: Scene.EarlyAccessFeature, + [urls.errorTracking()]: Scene.ErrorTracking, [urls.surveys()]: Scene.Surveys, [urls.survey(':id')]: Scene.Survey, [urls.surveyTemplates()]: Scene.SurveyTemplates, diff --git a/frontend/src/scenes/session-recordings/player/controller/Seekbar.scss b/frontend/src/scenes/session-recordings/player/controller/Seekbar.scss index 943f17aa977ba..8549d99d48dde 100644 --- a/frontend/src/scenes/session-recordings/player/controller/Seekbar.scss +++ b/frontend/src/scenes/session-recordings/player/controller/Seekbar.scss @@ -47,7 +47,7 @@ .PlayerSeekbar__currentbar { z-index: 3; - background-color: var(--recording-seekbar-red); + background-color: var(--primary-3000); border-radius: var(--bar-height) 0 0 var(--bar-height); } @@ -76,7 +76,7 @@ width: var(--thumb-size); height: var(--thumb-size); margin-top: calc(var(--thumb-size) / 2 * -1); - background-color: var(--recording-seekbar-red); + background-color: var(--primary-3000); border: 2px solid var(--bg-light); border-radius: 50%; transition: top 150ms ease-in-out; diff --git a/frontend/src/scenes/session-recordings/player/inspector/components/ItemEvent.tsx b/frontend/src/scenes/session-recordings/player/inspector/components/ItemEvent.tsx index e851684d58874..9131ba82271d2 100644 --- a/frontend/src/scenes/session-recordings/player/inspector/components/ItemEvent.tsx +++ b/frontend/src/scenes/session-recordings/player/inspector/components/ItemEvent.tsx @@ -68,7 +68,7 @@ export function ItemEvent({ item, expanded, setExpanded }: ItemEventProps): JSX. {item.data.fullyLoaded ? ( item.data.event === '$exception' ? ( - + ) : ( ) diff --git a/frontend/src/scenes/session-recordings/player/playerSettingsLogic.ts b/frontend/src/scenes/session-recordings/player/playerSettingsLogic.ts index 65829d1257afd..965e6f33382e3 100644 --- a/frontend/src/scenes/session-recordings/player/playerSettingsLogic.ts +++ b/frontend/src/scenes/session-recordings/player/playerSettingsLogic.ts @@ -1,5 +1,6 @@ -import { actions, kea, listeners, path, reducers, selectors } from 'kea' +import { actions, connect, kea, listeners, path, reducers, selectors } from 'kea' import { eventUsageLogic } from 'lib/utils/eventUsageLogic' +import { teamLogic } from 'scenes/teamLogic' import { AutoplayDirection, DurationType, SessionRecordingPlayerTab } from '~/types' @@ -191,7 +192,10 @@ export const playerSettingsLogic = kea([ setQuickFilterProperties: (properties: string[]) => ({ properties }), setTimestampFormat: (format: TimestampFormat) => ({ format }), }), - reducers(() => ({ + connect({ + values: [teamLogic, ['currentTeam']], + }), + reducers(({ values }) => ({ showFilters: [ true, { @@ -211,7 +215,7 @@ export const playerSettingsLogic = kea([ }, ], quickFilterProperties: [ - ['$geoip_country_name'] as string[], + ['$geoip_country_name', ...(values.currentTeam?.person_display_name_properties || [])] as string[], { persist: true, }, diff --git a/frontend/src/scenes/session-recordings/playlist/SessionRecordingsPlaylist.tsx b/frontend/src/scenes/session-recordings/playlist/SessionRecordingsPlaylist.tsx index 40d3d356bd447..17ae678e31792 100644 --- a/frontend/src/scenes/session-recordings/playlist/SessionRecordingsPlaylist.tsx +++ b/frontend/src/scenes/session-recordings/playlist/SessionRecordingsPlaylist.tsx @@ -23,6 +23,7 @@ import { urls } from 'scenes/urls' import { ReplayTabs, SessionRecordingType } from '~/types' +import { RecordingsUniversalFilters } from '../filters/RecordingsUniversalFilters' import { SessionRecordingsFilters } from '../filters/SessionRecordingsFilters' import { SessionRecordingPlayer } from '../player/SessionRecordingPlayer' import { SessionRecordingPreview, SessionRecordingPreviewSkeleton } from './SessionRecordingPreview' @@ -118,6 +119,7 @@ function RecordingsLists(): JSX.Element { recordingsCount, isRecordingsListCollapsed, sessionSummaryLoading, + useUniversalFiltering, } = useValues(sessionRecordingsPlaylistLogic) const { setSelectedRecordingId, @@ -205,25 +207,27 @@ function RecordingsLists(): JSX.Element { - - - - } - onClick={() => { - if (notebookNode) { - notebookNode.actions.toggleEditing() - } else { - setShowFilters(!showFilters) + {(!useUniversalFiltering || notebookNode) && ( + + + } - }} - > - Filter - + onClick={() => { + if (notebookNode) { + notebookNode.actions.toggleEditing() + } else { + setShowFilters(!showFilters) + } + }} + > + Filter + + )} - + +
+ {useUniversalFiltering && } +
- - +
+
) } diff --git a/frontend/src/scenes/session-recordings/playlist/sessionRecordingsPlaylistLogic.ts b/frontend/src/scenes/session-recordings/playlist/sessionRecordingsPlaylistLogic.ts index 6f128876501c8..7d8b34203a403 100644 --- a/frontend/src/scenes/session-recordings/playlist/sessionRecordingsPlaylistLogic.ts +++ b/frontend/src/scenes/session-recordings/playlist/sessionRecordingsPlaylistLogic.ts @@ -4,6 +4,10 @@ import { loaders } from 'kea-loaders' import { actionToUrl, router, urlToAction } from 'kea-router' import { subscriptions } from 'kea-subscriptions' import api from 'lib/api' +import { isAnyPropertyfilter } from 'lib/components/PropertyFilters/utils' +import { UniversalFiltersGroup, UniversalFilterValue } from 'lib/components/UniversalFilters/UniversalFilters' +import { DEFAULT_UNIVERSAL_GROUP_FILTER } from 'lib/components/UniversalFilters/universalFiltersLogic' +import { isActionFilter, isEventFilter } from 'lib/components/UniversalFilters/utils' import { FEATURE_FLAGS } from 'lib/constants' import { now } from 'lib/dayjs' import { featureFlagLogic } from 'lib/logic/featureFlagLogic' @@ -12,11 +16,15 @@ import { eventUsageLogic } from 'lib/utils/eventUsageLogic' import posthog from 'posthog-js' import { + AnyPropertyFilter, DurationType, + FilterableLogLevel, + FilterType, PropertyFilterType, PropertyOperator, RecordingDurationFilter, RecordingFilters, + RecordingUniversalFilters, ReplayTabs, SessionRecordingId, SessionRecordingsResponse, @@ -85,6 +93,14 @@ export const DEFAULT_RECORDING_FILTERS: RecordingFilters = { console_search_query: '', } +export const DEFAULT_RECORDING_UNIVERSAL_FILTERS: RecordingUniversalFilters = { + live_mode: false, + filter_test_accounts: false, + date_from: '-3d', + filter_group: { ...DEFAULT_UNIVERSAL_GROUP_FILTER }, + duration: [defaultRecordingDurationFilter], +} + const DEFAULT_PERSON_RECORDING_FILTERS: RecordingFilters = { ...DEFAULT_RECORDING_FILTERS, date_from: '-30d', @@ -106,6 +122,47 @@ const capturePartialFilters = (filters: Partial): void => { ...partialFilters, }) } +function convertUniversalFiltersToLegacyFilters(universalFilters: RecordingUniversalFilters): RecordingFilters { + const nestedFilters = universalFilters.filter_group.values[0] as UniversalFiltersGroup + const filters = nestedFilters.values as UniversalFilterValue[] + + const properties: AnyPropertyFilter[] = [] + const events: FilterType['events'] = [] + const actions: FilterType['actions'] = [] + let console_logs: FilterableLogLevel[] = [] + let console_search_query = '' + + filters.forEach((f) => { + if (isEventFilter(f)) { + events.push(f) + } else if (isActionFilter(f)) { + actions.push(f) + } else if (isAnyPropertyfilter(f)) { + if (f.type === PropertyFilterType.Recording) { + if (f.key === 'console_log_level') { + console_logs = f.value as FilterableLogLevel[] + } else if (f.key === 'console_log_query') { + console_search_query = (f.value || '') as string + } + } else { + properties.push(f) + } + } + }) + + const durationFilter = universalFilters.duration[0] + + return { + ...universalFilters, + properties, + events, + actions, + session_recording_duration: { ...durationFilter, key: 'duration' }, + duration_type_filter: durationFilter.key, + console_search_query, + console_logs, + } +} export interface SessionRecordingPlaylistLogicProps { logicKey?: string @@ -113,6 +170,7 @@ export interface SessionRecordingPlaylistLogicProps { updateSearchParams?: boolean autoPlay?: boolean hideSimpleFilters?: boolean + universalFilters?: RecordingUniversalFilters advancedFilters?: RecordingFilters simpleFilters?: RecordingFilters onFiltersChange?: (filters: RecordingFilters) => void @@ -148,6 +206,7 @@ export const sessionRecordingsPlaylistLogic = kea) => ({ filters }), setAdvancedFilters: (filters: Partial) => ({ filters }), setSimpleFilters: (filters: SimpleFiltersType) => ({ filters }), setShowFilters: (showFilters: boolean) => ({ showFilters }), @@ -355,6 +414,18 @@ export const sessionRecordingsPlaylistLogic = kea getDefaultFilters(props.personUUID), }, ], + universalFilters: [ + props.universalFilters ?? DEFAULT_RECORDING_UNIVERSAL_FILTERS, + { + setUniversalFilters: (state, { filters }) => { + return { + ...state, + ...filters, + } + }, + resetFilters: () => DEFAULT_RECORDING_UNIVERSAL_FILTERS, + }, + ], showFilters: [ true, { @@ -465,6 +536,12 @@ export const sessionRecordingsPlaylistLogic = kea { + actions.loadSessionRecordings() + props.onFiltersChange?.(values.filters) + capturePartialFilters(filters) + actions.loadEventsHaveSessionId() + }, setOrderBy: () => { actions.loadSessionRecordings() @@ -512,12 +589,20 @@ export const sessionRecordingsPlaylistLogic = kea [s.featureFlags], (featureFlags) => !!featureFlags[FEATURE_FLAGS.SESSION_REPLAY_HOG_QL_FILTERING], ], + useUniversalFiltering: [ + (s) => [s.featureFlags], + (featureFlags) => !!featureFlags[FEATURE_FLAGS.SESSION_REPLAY_UNIVERSAL_FILTERS], + ], logicProps: [() => [(_, props) => props], (props): SessionRecordingPlaylistLogicProps => props], filters: [ - (s) => [s.simpleFilters, s.advancedFilters], - (simpleFilters, advancedFilters): RecordingFilters => { + (s) => [s.simpleFilters, s.advancedFilters, s.universalFilters, s.featureFlags], + (simpleFilters, advancedFilters, universalFilters, featureFlags): RecordingFilters => { + if (featureFlags[FEATURE_FLAGS.SESSION_REPLAY_UNIVERSAL_FILTERS]) { + return convertUniversalFiltersToLegacyFilters(universalFilters) + } + return { ...advancedFilters, events: [...(simpleFilters?.events || []), ...(advancedFilters?.events || [])], diff --git a/frontend/src/scenes/settings/user/personalAPIKeysLogic.tsx b/frontend/src/scenes/settings/user/personalAPIKeysLogic.tsx index d0da4e1b4f7ee..caf5e06889346 100644 --- a/frontend/src/scenes/settings/user/personalAPIKeysLogic.tsx +++ b/frontend/src/scenes/settings/user/personalAPIKeysLogic.tsx @@ -306,7 +306,9 @@ export const personalAPIKeysLogic = kea([ <>

You can now use key "{key.label}" for authentication:

- {value} + + {value} + For security reasons the value above will never be shown again. diff --git a/frontend/src/scenes/surveys/SurveyEditQuestionRow.tsx b/frontend/src/scenes/surveys/SurveyEditQuestionRow.tsx index 4b43be07bcf46..ec3b03d54b41d 100644 --- a/frontend/src/scenes/surveys/SurveyEditQuestionRow.tsx +++ b/frontend/src/scenes/surveys/SurveyEditQuestionRow.tsx @@ -7,12 +7,15 @@ import { IconPlusSmall, IconTrash } from '@posthog/icons' import { LemonButton, LemonCheckbox, LemonInput, LemonSelect } from '@posthog/lemon-ui' import { useActions, useValues } from 'kea' import { Group } from 'kea-forms' +import { FEATURE_FLAGS } from 'lib/constants' import { SortableDragIcon } from 'lib/lemon-ui/icons' import { LemonField } from 'lib/lemon-ui/LemonField' +import { featureFlagLogic as enabledFeaturesLogic } from 'lib/logic/featureFlagLogic' import { Survey, SurveyQuestionType } from '~/types' import { defaultSurveyFieldValues, NewSurvey, SurveyQuestionLabel } from './constants' +import { QuestionBranchingInput } from './QuestionBranchingInput' import { HTMLEditor } from './SurveyAppearanceUtils' import { surveyLogic } from './surveyLogic' @@ -85,6 +88,10 @@ export function SurveyEditQuestionHeader({ export function SurveyEditQuestionGroup({ index, question }: { index: number; question: any }): JSX.Element { const { survey, descriptionContentType } = useValues(surveyLogic) const { setDefaultForQuestionType, setSurveyValue } = useActions(surveyLogic) + const { featureFlags } = useValues(enabledFeaturesLogic) + const hasBranching = + featureFlags[FEATURE_FLAGS.SURVEYS_BRANCHING_LOGIC] && + (question.type === SurveyQuestionType.Rating || question.type === SurveyQuestionType.SingleChoice) const initialDescriptionContentType = descriptionContentType(index) ?? 'text' @@ -332,6 +339,7 @@ export function SurveyEditQuestionGroup({ index, question }: { index: number; qu } /> + {hasBranching && }
) diff --git a/frontend/src/scenes/surveys/surveyLogic.tsx b/frontend/src/scenes/surveys/surveyLogic.tsx index c976e7f5dfb53..9b3964b64fdf3 100644 --- a/frontend/src/scenes/surveys/surveyLogic.tsx +++ b/frontend/src/scenes/surveys/surveyLogic.tsx @@ -16,10 +16,13 @@ import { hogql } from '~/queries/utils' import { Breadcrumb, FeatureFlagFilters, + MultipleSurveyQuestion, PropertyFilterType, PropertyOperator, + RatingSurveyQuestion, Survey, SurveyQuestionBase, + SurveyQuestionBranchingType, SurveyQuestionType, SurveyUrlMatchType, } from '~/types' @@ -154,6 +157,7 @@ export const surveyLogic = kea([ isEditingDescription, isEditingThankYouMessage, }), + setQuestionBranching: (questionIndex, value) => ({ questionIndex, value }), archiveSurvey: true, setWritingHTMLDescription: (writingHTML: boolean) => ({ writingHTML }), setSurveyTemplateValues: (template: any) => ({ template }), @@ -657,6 +661,44 @@ export const surveyLogic = kea([ const newTemplateSurvey = { ...NEW_SURVEY, ...template } return newTemplateSurvey }, + setQuestionBranching: (state, { questionIndex, value }) => { + const newQuestions = [...state.questions] + const question = newQuestions[questionIndex] + + if ( + question.type !== SurveyQuestionType.Rating && + question.type !== SurveyQuestionType.SingleChoice + ) { + throw new Error( + `Survey question type must be ${SurveyQuestionType.Rating} or ${SurveyQuestionType.SingleChoice}` + ) + } + + if (value === SurveyQuestionBranchingType.NextQuestion) { + delete question.branching + } else if (value === SurveyQuestionBranchingType.ConfirmationMessage) { + question.branching = { + type: SurveyQuestionBranchingType.ConfirmationMessage, + } + } else if (value === SurveyQuestionBranchingType.ResponseBased) { + question.branching = { + type: SurveyQuestionBranchingType.ResponseBased, + responseValue: {}, + } + } else if (value.startsWith(SurveyQuestionBranchingType.SpecificQuestion)) { + const nextQuestionIndex = parseInt(value.split(':')[1]) + question.branching = { + type: SurveyQuestionBranchingType.SpecificQuestion, + index: nextQuestionIndex, + } + } + + newQuestions[questionIndex] = question + return { + ...state, + questions: newQuestions, + } + }, }, ], selectedPageIndex: [ @@ -882,6 +924,28 @@ export const surveyLogic = kea([ } }, ], + getBranchingDropdownValue: [ + (s) => [s.survey], + (survey) => (questionIndex: number, question: RatingSurveyQuestion | MultipleSurveyQuestion) => { + if (question.branching?.type) { + const { type } = question.branching + + if (type === SurveyQuestionBranchingType.SpecificQuestion) { + const nextQuestionIndex = question.branching.index + return `${SurveyQuestionBranchingType.SpecificQuestion}:${nextQuestionIndex}` + } + + return type + } + + // No branching specified, default to Next question / Confirmation message + if (questionIndex < survey.questions.length - 1) { + return SurveyQuestionBranchingType.NextQuestion + } + + return SurveyQuestionBranchingType.ConfirmationMessage + }, + ], }), forms(({ actions, props, values }) => ({ survey: { diff --git a/frontend/src/scenes/urls.ts b/frontend/src/scenes/urls.ts index cc70cd9fc7f43..c5c68db5b337c 100644 --- a/frontend/src/scenes/urls.ts +++ b/frontend/src/scenes/urls.ts @@ -120,13 +120,13 @@ export const urls = { encode ? `/persons/${encodeURIComponent(uuid)}` : `/persons/${uuid}`, persons: (): string => '/persons', pipelineNodeDataWarehouseNew: (): string => `/pipeline/new/data-warehouse`, - pipelineNodeNew: (stage: PipelineStage | ':stage', pluginIdOrBatchExportDestination?: string | number): string => { + pipelineNodeNew: (stage: PipelineStage | ':stage', id?: string | number): string => { if (stage === PipelineStage.DataImport) { // should match 'pipelineNodeDataWarehouseNew' return `/pipeline/new/data-warehouse` } - return `/pipeline/new/${stage}${pluginIdOrBatchExportDestination ? `/${pluginIdOrBatchExportDestination}` : ''}` + return `/pipeline/new/${stage}${id ? `/${id}` : ''}` }, pipeline: (tab?: PipelineTab | ':tab'): string => `/pipeline/${tab ? tab : PipelineTab.Overview}`, /** @param id 'new' for new, uuid for batch exports and numbers for plugins */ @@ -149,6 +149,8 @@ export const urls = { earlyAccessFeatures: (): string => '/early_access_features', /** @param id A UUID or 'new'. ':id' for routing. */ earlyAccessFeature: (id: string): string => `/early_access_features/${id}`, + errorTracking: (): string => '/error_tracking', + errorTrackingGroup: (id: string): string => `/error_tracking/${id}`, surveys: (): string => '/surveys', /** @param id A UUID or 'new'. ':id' for routing. */ survey: (id: string): string => `/surveys/${id}`, diff --git a/frontend/src/scenes/web-analytics/WebAnalyticsNotice.tsx b/frontend/src/scenes/web-analytics/WebAnalyticsNotice.tsx index 2e8727a6f5048..fbe32a7e3b359 100644 --- a/frontend/src/scenes/web-analytics/WebAnalyticsNotice.tsx +++ b/frontend/src/scenes/web-analytics/WebAnalyticsNotice.tsx @@ -23,14 +23,14 @@ export const WebAnalyticsNotice = (): JSX.Element => { } - onClick={() => openSupportForm({ kind: 'bug' })} + onClick={() => openSupportForm({ kind: 'bug', isEmailFormOpen: true })} > Report a bug } - onClick={() => openSupportForm({ kind: 'feedback' })} + onClick={() => openSupportForm({ kind: 'feedback', isEmailFormOpen: true })} > Give feedback diff --git a/frontend/src/scenes/web-analytics/WebAnalyticsTile.tsx b/frontend/src/scenes/web-analytics/WebAnalyticsTile.tsx index 62e3cce87f7fb..6dec7396bfc17 100644 --- a/frontend/src/scenes/web-analytics/WebAnalyticsTile.tsx +++ b/frontend/src/scenes/web-analytics/WebAnalyticsTile.tsx @@ -330,7 +330,7 @@ export const WebStatsTrendTile = ({ }, [onWorldMapClick, insightProps]) return ( -
+
{showIntervalTile && (
diff --git a/frontend/src/styles/vars.scss b/frontend/src/styles/vars.scss index 783611a4d6a56..8758149290917 100644 --- a/frontend/src/styles/vars.scss +++ b/frontend/src/styles/vars.scss @@ -156,13 +156,11 @@ $colors: ( // These vars are modified via SCSS for legacy reasons (e.g. darken/lighten), so keeping as SCSS vars for now. $_primary: map.get($colors, 'primary'); $_success: map.get($colors, 'success'); -$_danger: map.get($colors, 'danger'); -$_primary_bg_hover: rgba($_primary, 0.1); $_primary_bg_active: rgba($_primary, 0.2); $_lifecycle_new: $_primary; $_lifecycle_returning: $_success; $_lifecycle_resurrecting: #a56eff; // --data-lilac -$_lifecycle_dormant: $_danger; +$_lifecycle_dormant: map.get($colors, 'danger'); // root variables are defined as a mixin here because // the toolbar needs them attached to :host not :root @@ -193,9 +191,6 @@ $_lifecycle_dormant: $_danger; --green: var(--success); --black: var(--default); - // Tag colors - --purple-light: #dcb1e3; - //// Data colors (e.g. insight series). Note: colors.ts relies on these values being hexadecimal --data-color-1: #1d4aff; --data-color-2: #621da6; @@ -227,22 +222,6 @@ $_lifecycle_dormant: $_danger; // TODO: unify with lib/colors.ts, getGraphColors() --funnel-axis: var(--border); --funnel-grid: #ddd; - --antd-table-background-dark: #fafafa; - - // Session Recording - --recording-spacing: calc(2rem / 3); - --recording-player-container-bg: #797973; - --recording-buffer-bg: #faaf8c; - --recording-seekbar-red: var(--brand-red); - --recording-hover-event: var(--primary-bg-hover); - --recording-hover-event-mid: var(--primary-bg-active); - --recording-hover-event-dark: var(--primary-3000); - --recording-current-event: #eef2ff; - --recording-current-event-dark: var(--primary-alt); - --recording-failure-event: #fee9e2; - --recording-failure-event-dark: #cd3000; - --recording-highlight-event: var(--mark); - --recording-highlight-event-dark: #946508; // Z-indexes --z-bottom-notice: 5100; diff --git a/frontend/src/types.ts b/frontend/src/types.ts index ab2fb248d8fd7..160a46df07749 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -5,6 +5,7 @@ import { ChartDataset, ChartType, InteractionItem } from 'chart.js' import { LogicWrapper } from 'kea' import { DashboardCompatibleScenes } from 'lib/components/SceneDashboardChoice/sceneDashboardChoiceModalLogic' import { TaxonomicFilterGroupType } from 'lib/components/TaxonomicFilter/types' +import { UniversalFiltersGroup } from 'lib/components/UniversalFilters/UniversalFilters' import { BIN_COUNT_AUTO, DashboardPrivilegeLevel, @@ -777,7 +778,7 @@ export type AnyPropertyFilter = | ElementPropertyFilter | SessionPropertyFilter | CohortPropertyFilter - | RecordingDurationFilter + | RecordingPropertyFilter | GroupPropertyFilter | FeaturePropertyFilter | HogQLPropertyFilter @@ -946,13 +947,17 @@ export type ActionStepProperties = | ElementPropertyFilter | CohortPropertyFilter -export interface RecordingDurationFilter extends BasePropertyFilter { +export interface RecordingPropertyFilter extends BasePropertyFilter { type: PropertyFilterType.Recording - key: 'duration' - value: number + key: DurationType | 'console_log_level' | 'console_log_query' operator: PropertyOperator } +export interface RecordingDurationFilter extends RecordingPropertyFilter { + key: DurationType + value: number +} + export type DurationType = 'duration' | 'active_seconds' | 'inactive_seconds' export type FilterableLogLevel = 'info' | 'warn' | 'error' @@ -973,6 +978,18 @@ export interface RecordingFilters { filter_test_accounts?: boolean } +export interface RecordingUniversalFilters { + /** + * live mode is front end only, sets date_from and date_to to the last hour + */ + live_mode?: boolean + date_from?: string | null + date_to?: string | null + duration: RecordingDurationFilter[] + filter_test_accounts?: boolean + filter_group: UniversalFiltersGroup +} + export interface SessionRecordingsResponse { results: SessionRecordingType[] has_next: boolean @@ -989,6 +1006,13 @@ export type ErrorCluster = { } export type ErrorClusterResponse = ErrorCluster[] | null +export type ErrorTrackingGroup = { + title: string + sampleEventProperties: EventType['properties'] + occurrences: number + uniqueSessions: number +} + export type EntityType = 'actions' | 'events' | 'data_warehouse' | 'new_entity' export interface Entity { @@ -2649,6 +2673,11 @@ export interface RatingSurveyQuestion extends SurveyQuestionBase { scale: number lowerBoundLabel: string upperBoundLabel: string + branching?: + | NextQuestionBranching + | ConfirmationMessageBranching + | ResponseBasedBranching + | SpecificQuestionBranching } export interface MultipleSurveyQuestion extends SurveyQuestionBase { @@ -2656,6 +2685,11 @@ export interface MultipleSurveyQuestion extends SurveyQuestionBase { choices: string[] shuffleOptions?: boolean hasOpenChoice?: boolean + branching?: + | NextQuestionBranching + | ConfirmationMessageBranching + | ResponseBasedBranching + | SpecificQuestionBranching } export type SurveyQuestion = BasicSurveyQuestion | LinkSurveyQuestion | RatingSurveyQuestion | MultipleSurveyQuestion @@ -2668,6 +2702,31 @@ export enum SurveyQuestionType { Link = 'link', } +export enum SurveyQuestionBranchingType { + NextQuestion = 'next_question', + ConfirmationMessage = 'confirmation_message', + ResponseBased = 'response_based', + SpecificQuestion = 'specific_question', +} + +interface NextQuestionBranching { + type: SurveyQuestionBranchingType.NextQuestion +} + +interface ConfirmationMessageBranching { + type: SurveyQuestionBranchingType.ConfirmationMessage +} + +interface ResponseBasedBranching { + type: SurveyQuestionBranchingType.ResponseBased + responseValue: Record +} + +interface SpecificQuestionBranching { + type: SurveyQuestionBranchingType.SpecificQuestion + index: number +} + export interface FeatureFlagGroupType { properties?: AnyPropertyFilter[] rollout_percentage?: number | null @@ -3746,6 +3805,7 @@ export interface ExternalDataStripeSource { prefix: string last_run_at?: Dayjs schemas: ExternalDataSourceSchema[] + sync_frequency: DataWarehouseSyncInterval } export interface SimpleExternalDataSourceSchema { id: string @@ -3879,6 +3939,8 @@ export type BatchExportService = export type PipelineInterval = 'hour' | 'day' | 'every 5 minutes' +export type DataWarehouseSyncInterval = 'day' | 'week' | 'month' + export type BatchExportConfiguration = { // User provided data for the export. This is the data that the user // provides when creating the export. @@ -4088,6 +4150,44 @@ export type OnboardingProduct = { scene: Scene } +export type HogFunctionInputSchemaType = { + type: 'string' | 'boolean' | 'dictionary' | 'choice' | 'json' + key: string + label: string + choices?: { value: string; label: string }[] + required?: boolean + default?: any + secret?: boolean + description?: string +} + +export type HogFunctionType = { + id: string + name: string + description: string + created_by: UserBasicType | null + created_at: string + updated_at: string + enabled: boolean + hog: string + + inputs_schema: HogFunctionInputSchemaType[] + inputs: Record< + string, + { + value: any + bytecode?: any + } + > + filters?: PluginConfigFilters | null + template?: HogFunctionTemplateType +} + +export type HogFunctionTemplateType = Pick< + HogFunctionType, + 'id' | 'name' | 'description' | 'hog' | 'inputs_schema' | 'filters' +> + export interface AnomalyCondition { absoluteThreshold: { lower?: number diff --git a/hogql_parser/HogQLParser.cpp b/hogql_parser/HogQLParser.cpp index ff90358df9bd2..112fd7cb48ae0 100644 --- a/hogql_parser/HogQLParser.cpp +++ b/hogql_parser/HogQLParser.cpp @@ -113,7 +113,7 @@ void hogqlparserParserInitialize() { } ); static const int32_t serializedATNSegment[] = { - 4,1,154,1158,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, + 4,1,154,1178,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, @@ -171,52 +171,54 @@ void hogqlparserParserInitialize() { 1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53, 1,53,1,53,1,53,1,53,1,53,1,53,1,53,3,53,724,8,53,1,53,1,53,1,53,1,53, 1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,3,53,741,8,53, - 1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,3,53,753,8,53,1,53, - 1,53,1,53,1,53,1,53,1,53,1,53,1,53,3,53,763,8,53,1,53,3,53,766,8,53,1, - 53,1,53,3,53,770,8,53,1,53,3,53,773,8,53,1,53,1,53,1,53,1,53,1,53,1,53, - 1,53,1,53,1,53,1,53,1,53,1,53,3,53,787,8,53,1,53,1,53,1,53,1,53,1,53, - 1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,3,53,804,8,53,1,53, - 1,53,1,53,3,53,809,8,53,1,53,1,53,3,53,813,8,53,1,53,1,53,1,53,1,53,3, - 53,819,8,53,1,53,1,53,1,53,1,53,1,53,3,53,826,8,53,1,53,1,53,1,53,1,53, - 1,53,1,53,1,53,1,53,1,53,1,53,3,53,838,8,53,1,53,1,53,3,53,842,8,53,1, - 53,3,53,845,8,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,3,53,854,8,53,1,53, - 1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,3,53,868,8,53, + 1,53,1,53,1,53,1,53,3,53,747,8,53,1,53,3,53,750,8,53,1,53,3,53,753,8, + 53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,3,53,763,8,53,1,53,1,53,1, + 53,1,53,3,53,769,8,53,1,53,3,53,772,8,53,1,53,3,53,775,8,53,1,53,1,53, + 1,53,1,53,1,53,1,53,3,53,783,8,53,1,53,3,53,786,8,53,1,53,1,53,3,53,790, + 8,53,1,53,3,53,793,8,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53, + 1,53,1,53,1,53,3,53,807,8,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53, + 1,53,1,53,1,53,1,53,1,53,1,53,1,53,3,53,824,8,53,1,53,1,53,1,53,3,53, + 829,8,53,1,53,1,53,3,53,833,8,53,1,53,1,53,1,53,1,53,3,53,839,8,53,1, + 53,1,53,1,53,1,53,1,53,3,53,846,8,53,1,53,1,53,1,53,1,53,1,53,1,53,1, + 53,1,53,1,53,1,53,3,53,858,8,53,1,53,1,53,3,53,862,8,53,1,53,3,53,865, + 8,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,3,53,874,8,53,1,53,1,53,1,53, + 1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,3,53,888,8,53,1,53,1,53, 1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53, - 1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,3,53,895,8,53, - 1,53,1,53,1,53,1,53,1,53,1,53,1,53,3,53,904,8,53,5,53,906,8,53,10,53, - 12,53,909,9,53,1,54,1,54,1,54,5,54,914,8,54,10,54,12,54,917,9,54,1,55, - 1,55,3,55,921,8,55,1,56,1,56,1,56,1,56,5,56,927,8,56,10,56,12,56,930, - 9,56,1,56,1,56,1,56,1,56,1,56,5,56,937,8,56,10,56,12,56,940,9,56,3,56, - 942,8,56,1,56,1,56,1,56,1,57,1,57,1,57,5,57,950,8,57,10,57,12,57,953, - 9,57,1,57,1,57,1,57,1,57,1,57,1,57,5,57,961,8,57,10,57,12,57,964,9,57, - 1,57,1,57,3,57,968,8,57,1,57,1,57,1,57,1,57,1,57,3,57,975,8,57,1,58,1, - 58,1,58,1,58,1,58,1,58,1,58,1,58,1,58,1,58,1,58,3,58,988,8,58,1,59,1, - 59,1,59,5,59,993,8,59,10,59,12,59,996,9,59,1,60,1,60,1,60,1,60,1,60,1, - 60,1,60,1,60,1,60,1,60,3,60,1008,8,60,1,61,1,61,1,61,1,61,3,61,1014,8, - 61,1,61,3,61,1017,8,61,1,62,1,62,1,62,5,62,1022,8,62,10,62,12,62,1025, - 9,62,1,63,1,63,1,63,1,63,1,63,1,63,1,63,1,63,1,63,3,63,1036,8,63,1,63, - 1,63,1,63,1,63,3,63,1042,8,63,5,63,1044,8,63,10,63,12,63,1047,9,63,1, - 64,1,64,1,64,3,64,1052,8,64,1,64,1,64,1,65,1,65,1,65,3,65,1059,8,65,1, - 65,1,65,1,66,1,66,1,66,5,66,1066,8,66,10,66,12,66,1069,9,66,1,67,1,67, - 1,68,1,68,1,68,1,68,1,68,1,68,3,68,1079,8,68,3,68,1081,8,68,1,69,3,69, - 1084,8,69,1,69,1,69,1,69,1,69,1,69,1,69,3,69,1092,8,69,1,70,1,70,1,70, - 3,70,1097,8,70,1,71,1,71,1,72,1,72,1,73,1,73,1,74,1,74,3,74,1107,8,74, - 1,75,1,75,1,75,3,75,1112,8,75,1,76,1,76,1,76,1,76,1,77,1,77,1,77,1,77, - 1,78,1,78,3,78,1124,8,78,1,79,1,79,5,79,1128,8,79,10,79,12,79,1131,9, - 79,1,79,1,79,1,80,1,80,1,80,1,80,1,80,3,80,1140,8,80,1,81,1,81,5,81,1144, - 8,81,10,81,12,81,1147,9,81,1,81,1,81,1,82,1,82,1,82,1,82,1,82,3,82,1156, - 8,82,1,82,0,3,68,106,126,83,0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30, - 32,34,36,38,40,42,44,46,48,50,52,54,56,58,60,62,64,66,68,70,72,74,76, - 78,80,82,84,86,88,90,92,94,96,98,100,102,104,106,108,110,112,114,116, - 118,120,122,124,126,128,130,132,134,136,138,140,142,144,146,148,150,152, - 154,156,158,160,162,164,0,16,2,0,17,17,72,72,2,0,42,42,49,49,3,0,1,1, - 4,4,8,8,4,0,1,1,3,4,8,8,78,78,2,0,49,49,71,71,2,0,1,1,4,4,2,0,7,7,21, - 22,2,0,28,28,47,47,2,0,69,69,74,74,3,0,10,10,48,48,87,87,2,0,39,39,51, - 51,1,0,103,104,2,0,114,114,134,134,7,0,20,20,36,36,53,54,68,68,76,76, - 93,93,99,99,12,0,1,19,21,28,30,35,37,40,42,49,51,52,56,56,58,67,69,75, - 77,92,94,95,97,98,4,0,19,19,28,28,37,37,46,46,1288,0,169,1,0,0,0,2,176, - 1,0,0,0,4,178,1,0,0,0,6,180,1,0,0,0,8,189,1,0,0,0,10,195,1,0,0,0,12,212, - 1,0,0,0,14,214,1,0,0,0,16,217,1,0,0,0,18,226,1,0,0,0,20,232,1,0,0,0,22, + 1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,3,53,915,8,53,1,53,1,53, + 1,53,1,53,1,53,1,53,1,53,3,53,924,8,53,5,53,926,8,53,10,53,12,53,929, + 9,53,1,54,1,54,1,54,5,54,934,8,54,10,54,12,54,937,9,54,1,55,1,55,3,55, + 941,8,55,1,56,1,56,1,56,1,56,5,56,947,8,56,10,56,12,56,950,9,56,1,56, + 1,56,1,56,1,56,1,56,5,56,957,8,56,10,56,12,56,960,9,56,3,56,962,8,56, + 1,56,1,56,1,56,1,57,1,57,1,57,5,57,970,8,57,10,57,12,57,973,9,57,1,57, + 1,57,1,57,1,57,1,57,1,57,5,57,981,8,57,10,57,12,57,984,9,57,1,57,1,57, + 3,57,988,8,57,1,57,1,57,1,57,1,57,1,57,3,57,995,8,57,1,58,1,58,1,58,1, + 58,1,58,1,58,1,58,1,58,1,58,1,58,1,58,3,58,1008,8,58,1,59,1,59,1,59,5, + 59,1013,8,59,10,59,12,59,1016,9,59,1,60,1,60,1,60,1,60,1,60,1,60,1,60, + 1,60,1,60,1,60,3,60,1028,8,60,1,61,1,61,1,61,1,61,3,61,1034,8,61,1,61, + 3,61,1037,8,61,1,62,1,62,1,62,5,62,1042,8,62,10,62,12,62,1045,9,62,1, + 63,1,63,1,63,1,63,1,63,1,63,1,63,1,63,1,63,3,63,1056,8,63,1,63,1,63,1, + 63,1,63,3,63,1062,8,63,5,63,1064,8,63,10,63,12,63,1067,9,63,1,64,1,64, + 1,64,3,64,1072,8,64,1,64,1,64,1,65,1,65,1,65,3,65,1079,8,65,1,65,1,65, + 1,66,1,66,1,66,5,66,1086,8,66,10,66,12,66,1089,9,66,1,67,1,67,1,68,1, + 68,1,68,1,68,1,68,1,68,3,68,1099,8,68,3,68,1101,8,68,1,69,3,69,1104,8, + 69,1,69,1,69,1,69,1,69,1,69,1,69,3,69,1112,8,69,1,70,1,70,1,70,3,70,1117, + 8,70,1,71,1,71,1,72,1,72,1,73,1,73,1,74,1,74,3,74,1127,8,74,1,75,1,75, + 1,75,3,75,1132,8,75,1,76,1,76,1,76,1,76,1,77,1,77,1,77,1,77,1,78,1,78, + 3,78,1144,8,78,1,79,1,79,5,79,1148,8,79,10,79,12,79,1151,9,79,1,79,1, + 79,1,80,1,80,1,80,1,80,1,80,3,80,1160,8,80,1,81,1,81,5,81,1164,8,81,10, + 81,12,81,1167,9,81,1,81,1,81,1,82,1,82,1,82,1,82,1,82,3,82,1176,8,82, + 1,82,0,3,68,106,126,83,0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32, + 34,36,38,40,42,44,46,48,50,52,54,56,58,60,62,64,66,68,70,72,74,76,78, + 80,82,84,86,88,90,92,94,96,98,100,102,104,106,108,110,112,114,116,118, + 120,122,124,126,128,130,132,134,136,138,140,142,144,146,148,150,152,154, + 156,158,160,162,164,0,16,2,0,17,17,72,72,2,0,42,42,49,49,3,0,1,1,4,4, + 8,8,4,0,1,1,3,4,8,8,78,78,2,0,49,49,71,71,2,0,1,1,4,4,2,0,7,7,21,22,2, + 0,28,28,47,47,2,0,69,69,74,74,3,0,10,10,48,48,87,87,2,0,39,39,51,51,1, + 0,103,104,2,0,114,114,134,134,7,0,20,20,36,36,53,54,68,68,76,76,93,93, + 99,99,12,0,1,19,21,28,30,35,37,40,42,49,51,52,56,56,58,67,69,75,77,92, + 94,95,97,98,4,0,19,19,28,28,37,37,46,46,1314,0,169,1,0,0,0,2,176,1,0, + 0,0,4,178,1,0,0,0,6,180,1,0,0,0,8,189,1,0,0,0,10,195,1,0,0,0,12,212,1, + 0,0,0,14,214,1,0,0,0,16,217,1,0,0,0,18,226,1,0,0,0,20,232,1,0,0,0,22, 236,1,0,0,0,24,245,1,0,0,0,26,247,1,0,0,0,28,256,1,0,0,0,30,260,1,0,0, 0,32,271,1,0,0,0,34,275,1,0,0,0,36,290,1,0,0,0,38,293,1,0,0,0,40,342, 1,0,0,0,42,345,1,0,0,0,44,351,1,0,0,0,46,355,1,0,0,0,48,361,1,0,0,0,50, @@ -226,14 +228,14 @@ void hogqlparserParserInitialize() { 541,1,0,0,0,80,549,1,0,0,0,82,567,1,0,0,0,84,569,1,0,0,0,86,577,1,0,0, 0,88,582,1,0,0,0,90,590,1,0,0,0,92,594,1,0,0,0,94,598,1,0,0,0,96,607, 1,0,0,0,98,621,1,0,0,0,100,623,1,0,0,0,102,673,1,0,0,0,104,675,1,0,0, - 0,106,812,1,0,0,0,108,910,1,0,0,0,110,920,1,0,0,0,112,941,1,0,0,0,114, - 974,1,0,0,0,116,987,1,0,0,0,118,989,1,0,0,0,120,1007,1,0,0,0,122,1016, - 1,0,0,0,124,1018,1,0,0,0,126,1035,1,0,0,0,128,1048,1,0,0,0,130,1058,1, - 0,0,0,132,1062,1,0,0,0,134,1070,1,0,0,0,136,1080,1,0,0,0,138,1083,1,0, - 0,0,140,1096,1,0,0,0,142,1098,1,0,0,0,144,1100,1,0,0,0,146,1102,1,0,0, - 0,148,1106,1,0,0,0,150,1111,1,0,0,0,152,1113,1,0,0,0,154,1117,1,0,0,0, - 156,1123,1,0,0,0,158,1125,1,0,0,0,160,1139,1,0,0,0,162,1141,1,0,0,0,164, - 1155,1,0,0,0,166,168,3,2,1,0,167,166,1,0,0,0,168,171,1,0,0,0,169,167, + 0,106,832,1,0,0,0,108,930,1,0,0,0,110,940,1,0,0,0,112,961,1,0,0,0,114, + 994,1,0,0,0,116,1007,1,0,0,0,118,1009,1,0,0,0,120,1027,1,0,0,0,122,1036, + 1,0,0,0,124,1038,1,0,0,0,126,1055,1,0,0,0,128,1068,1,0,0,0,130,1078,1, + 0,0,0,132,1082,1,0,0,0,134,1090,1,0,0,0,136,1100,1,0,0,0,138,1103,1,0, + 0,0,140,1116,1,0,0,0,142,1118,1,0,0,0,144,1120,1,0,0,0,146,1122,1,0,0, + 0,148,1126,1,0,0,0,150,1131,1,0,0,0,152,1133,1,0,0,0,154,1137,1,0,0,0, + 156,1143,1,0,0,0,158,1145,1,0,0,0,160,1159,1,0,0,0,162,1161,1,0,0,0,164, + 1175,1,0,0,0,166,168,3,2,1,0,167,166,1,0,0,0,168,171,1,0,0,0,169,167, 1,0,0,0,169,170,1,0,0,0,170,172,1,0,0,0,171,169,1,0,0,0,172,173,5,0,0, 1,173,1,1,0,0,0,174,177,3,6,3,0,175,177,3,12,6,0,176,174,1,0,0,0,176, 175,1,0,0,0,177,3,1,0,0,0,178,179,3,106,53,0,179,5,1,0,0,0,180,181,5, @@ -384,158 +386,165 @@ void hogqlparserParserInitialize() { 688,689,5,94,0,0,689,690,3,106,53,0,690,691,5,81,0,0,691,692,3,106,53, 0,692,694,1,0,0,0,693,688,1,0,0,0,694,695,1,0,0,0,695,693,1,0,0,0,695, 696,1,0,0,0,696,699,1,0,0,0,697,698,5,24,0,0,698,700,3,106,53,0,699,697, - 1,0,0,0,699,700,1,0,0,0,700,701,1,0,0,0,701,702,5,25,0,0,702,813,1,0, + 1,0,0,0,699,700,1,0,0,0,700,701,1,0,0,0,701,702,5,25,0,0,702,833,1,0, 0,0,703,704,5,13,0,0,704,705,5,126,0,0,705,706,3,106,53,0,706,707,5,6, - 0,0,707,708,3,102,51,0,708,709,5,144,0,0,709,813,1,0,0,0,710,711,5,19, - 0,0,711,813,5,106,0,0,712,713,5,43,0,0,713,714,3,106,53,0,714,715,3,142, - 71,0,715,813,1,0,0,0,716,717,5,80,0,0,717,718,5,126,0,0,718,719,3,106, + 0,0,707,708,3,102,51,0,708,709,5,144,0,0,709,833,1,0,0,0,710,711,5,19, + 0,0,711,833,5,106,0,0,712,713,5,43,0,0,713,714,3,106,53,0,714,715,3,142, + 71,0,715,833,1,0,0,0,716,717,5,80,0,0,717,718,5,126,0,0,718,719,3,106, 53,0,719,720,5,32,0,0,720,723,3,106,53,0,721,722,5,31,0,0,722,724,3,106, 53,0,723,721,1,0,0,0,723,724,1,0,0,0,724,725,1,0,0,0,725,726,5,144,0, - 0,726,813,1,0,0,0,727,728,5,83,0,0,728,813,5,106,0,0,729,730,5,88,0,0, + 0,726,833,1,0,0,0,727,728,5,83,0,0,728,833,5,106,0,0,729,730,5,88,0,0, 730,731,5,126,0,0,731,732,7,9,0,0,732,733,3,156,78,0,733,734,5,32,0,0, - 734,735,3,106,53,0,735,736,5,144,0,0,736,813,1,0,0,0,737,738,3,150,75, + 734,735,3,106,53,0,735,736,5,144,0,0,736,833,1,0,0,0,737,738,3,150,75, 0,738,740,5,126,0,0,739,741,3,104,52,0,740,739,1,0,0,0,740,741,1,0,0, - 0,741,742,1,0,0,0,742,743,5,144,0,0,743,744,1,0,0,0,744,745,5,64,0,0, - 745,746,5,126,0,0,746,747,3,88,44,0,747,748,5,144,0,0,748,813,1,0,0,0, - 749,750,3,150,75,0,750,752,5,126,0,0,751,753,3,104,52,0,752,751,1,0,0, - 0,752,753,1,0,0,0,753,754,1,0,0,0,754,755,5,144,0,0,755,756,1,0,0,0,756, - 757,5,64,0,0,757,758,3,150,75,0,758,813,1,0,0,0,759,765,3,150,75,0,760, - 762,5,126,0,0,761,763,3,104,52,0,762,761,1,0,0,0,762,763,1,0,0,0,763, - 764,1,0,0,0,764,766,5,144,0,0,765,760,1,0,0,0,765,766,1,0,0,0,766,767, - 1,0,0,0,767,769,5,126,0,0,768,770,5,23,0,0,769,768,1,0,0,0,769,770,1, - 0,0,0,770,772,1,0,0,0,771,773,3,108,54,0,772,771,1,0,0,0,772,773,1,0, - 0,0,773,774,1,0,0,0,774,775,5,144,0,0,775,813,1,0,0,0,776,813,3,114,57, - 0,777,813,3,158,79,0,778,813,3,140,70,0,779,780,5,114,0,0,780,813,3,106, - 53,19,781,782,5,56,0,0,782,813,3,106,53,13,783,784,3,130,65,0,784,785, - 5,116,0,0,785,787,1,0,0,0,786,783,1,0,0,0,786,787,1,0,0,0,787,788,1,0, - 0,0,788,813,5,108,0,0,789,790,5,126,0,0,790,791,3,34,17,0,791,792,5,144, - 0,0,792,813,1,0,0,0,793,794,5,126,0,0,794,795,3,106,53,0,795,796,5,144, - 0,0,796,813,1,0,0,0,797,798,5,126,0,0,798,799,3,104,52,0,799,800,5,144, - 0,0,800,813,1,0,0,0,801,803,5,125,0,0,802,804,3,104,52,0,803,802,1,0, - 0,0,803,804,1,0,0,0,804,805,1,0,0,0,805,813,5,143,0,0,806,808,5,124,0, - 0,807,809,3,30,15,0,808,807,1,0,0,0,808,809,1,0,0,0,809,810,1,0,0,0,810, - 813,5,142,0,0,811,813,3,122,61,0,812,683,1,0,0,0,812,703,1,0,0,0,812, - 710,1,0,0,0,812,712,1,0,0,0,812,716,1,0,0,0,812,727,1,0,0,0,812,729,1, - 0,0,0,812,737,1,0,0,0,812,749,1,0,0,0,812,759,1,0,0,0,812,776,1,0,0,0, - 812,777,1,0,0,0,812,778,1,0,0,0,812,779,1,0,0,0,812,781,1,0,0,0,812,786, - 1,0,0,0,812,789,1,0,0,0,812,793,1,0,0,0,812,797,1,0,0,0,812,801,1,0,0, - 0,812,806,1,0,0,0,812,811,1,0,0,0,813,907,1,0,0,0,814,818,10,18,0,0,815, - 819,5,108,0,0,816,819,5,146,0,0,817,819,5,133,0,0,818,815,1,0,0,0,818, - 816,1,0,0,0,818,817,1,0,0,0,819,820,1,0,0,0,820,906,3,106,53,19,821,825, - 10,17,0,0,822,826,5,134,0,0,823,826,5,114,0,0,824,826,5,113,0,0,825,822, - 1,0,0,0,825,823,1,0,0,0,825,824,1,0,0,0,826,827,1,0,0,0,827,906,3,106, - 53,18,828,853,10,16,0,0,829,854,5,117,0,0,830,854,5,118,0,0,831,854,5, - 129,0,0,832,854,5,127,0,0,833,854,5,128,0,0,834,854,5,119,0,0,835,854, - 5,120,0,0,836,838,5,56,0,0,837,836,1,0,0,0,837,838,1,0,0,0,838,839,1, - 0,0,0,839,841,5,40,0,0,840,842,5,14,0,0,841,840,1,0,0,0,841,842,1,0,0, - 0,842,854,1,0,0,0,843,845,5,56,0,0,844,843,1,0,0,0,844,845,1,0,0,0,845, - 846,1,0,0,0,846,854,7,10,0,0,847,854,5,140,0,0,848,854,5,141,0,0,849, - 854,5,131,0,0,850,854,5,122,0,0,851,854,5,123,0,0,852,854,5,130,0,0,853, - 829,1,0,0,0,853,830,1,0,0,0,853,831,1,0,0,0,853,832,1,0,0,0,853,833,1, - 0,0,0,853,834,1,0,0,0,853,835,1,0,0,0,853,837,1,0,0,0,853,844,1,0,0,0, - 853,847,1,0,0,0,853,848,1,0,0,0,853,849,1,0,0,0,853,850,1,0,0,0,853,851, - 1,0,0,0,853,852,1,0,0,0,854,855,1,0,0,0,855,906,3,106,53,17,856,857,10, - 14,0,0,857,858,5,132,0,0,858,906,3,106,53,15,859,860,10,12,0,0,860,861, - 5,2,0,0,861,906,3,106,53,13,862,863,10,11,0,0,863,864,5,61,0,0,864,906, - 3,106,53,12,865,867,10,10,0,0,866,868,5,56,0,0,867,866,1,0,0,0,867,868, - 1,0,0,0,868,869,1,0,0,0,869,870,5,9,0,0,870,871,3,106,53,0,871,872,5, - 2,0,0,872,873,3,106,53,11,873,906,1,0,0,0,874,875,10,9,0,0,875,876,5, - 135,0,0,876,877,3,106,53,0,877,878,5,111,0,0,878,879,3,106,53,9,879,906, - 1,0,0,0,880,881,10,22,0,0,881,882,5,125,0,0,882,883,3,106,53,0,883,884, - 5,143,0,0,884,906,1,0,0,0,885,886,10,21,0,0,886,887,5,116,0,0,887,906, - 5,104,0,0,888,889,10,20,0,0,889,890,5,116,0,0,890,906,3,150,75,0,891, - 892,10,15,0,0,892,894,5,44,0,0,893,895,5,56,0,0,894,893,1,0,0,0,894,895, - 1,0,0,0,895,896,1,0,0,0,896,906,5,57,0,0,897,903,10,8,0,0,898,904,3,148, - 74,0,899,900,5,6,0,0,900,904,3,150,75,0,901,902,5,6,0,0,902,904,5,106, - 0,0,903,898,1,0,0,0,903,899,1,0,0,0,903,901,1,0,0,0,904,906,1,0,0,0,905, - 814,1,0,0,0,905,821,1,0,0,0,905,828,1,0,0,0,905,856,1,0,0,0,905,859,1, - 0,0,0,905,862,1,0,0,0,905,865,1,0,0,0,905,874,1,0,0,0,905,880,1,0,0,0, - 905,885,1,0,0,0,905,888,1,0,0,0,905,891,1,0,0,0,905,897,1,0,0,0,906,909, - 1,0,0,0,907,905,1,0,0,0,907,908,1,0,0,0,908,107,1,0,0,0,909,907,1,0,0, - 0,910,915,3,110,55,0,911,912,5,112,0,0,912,914,3,110,55,0,913,911,1,0, - 0,0,914,917,1,0,0,0,915,913,1,0,0,0,915,916,1,0,0,0,916,109,1,0,0,0,917, - 915,1,0,0,0,918,921,3,112,56,0,919,921,3,106,53,0,920,918,1,0,0,0,920, - 919,1,0,0,0,921,111,1,0,0,0,922,923,5,126,0,0,923,928,3,150,75,0,924, - 925,5,112,0,0,925,927,3,150,75,0,926,924,1,0,0,0,927,930,1,0,0,0,928, - 926,1,0,0,0,928,929,1,0,0,0,929,931,1,0,0,0,930,928,1,0,0,0,931,932,5, - 144,0,0,932,942,1,0,0,0,933,938,3,150,75,0,934,935,5,112,0,0,935,937, - 3,150,75,0,936,934,1,0,0,0,937,940,1,0,0,0,938,936,1,0,0,0,938,939,1, - 0,0,0,939,942,1,0,0,0,940,938,1,0,0,0,941,922,1,0,0,0,941,933,1,0,0,0, - 942,943,1,0,0,0,943,944,5,107,0,0,944,945,3,106,53,0,945,113,1,0,0,0, - 946,947,5,128,0,0,947,951,3,150,75,0,948,950,3,116,58,0,949,948,1,0,0, - 0,950,953,1,0,0,0,951,949,1,0,0,0,951,952,1,0,0,0,952,954,1,0,0,0,953, - 951,1,0,0,0,954,955,5,146,0,0,955,956,5,120,0,0,956,975,1,0,0,0,957,958, - 5,128,0,0,958,962,3,150,75,0,959,961,3,116,58,0,960,959,1,0,0,0,961,964, - 1,0,0,0,962,960,1,0,0,0,962,963,1,0,0,0,963,965,1,0,0,0,964,962,1,0,0, - 0,965,967,5,120,0,0,966,968,3,114,57,0,967,966,1,0,0,0,967,968,1,0,0, - 0,968,969,1,0,0,0,969,970,5,128,0,0,970,971,5,146,0,0,971,972,3,150,75, - 0,972,973,5,120,0,0,973,975,1,0,0,0,974,946,1,0,0,0,974,957,1,0,0,0,975, - 115,1,0,0,0,976,977,3,150,75,0,977,978,5,118,0,0,978,979,3,156,78,0,979, - 988,1,0,0,0,980,981,3,150,75,0,981,982,5,118,0,0,982,983,5,124,0,0,983, - 984,3,106,53,0,984,985,5,142,0,0,985,988,1,0,0,0,986,988,3,150,75,0,987, - 976,1,0,0,0,987,980,1,0,0,0,987,986,1,0,0,0,988,117,1,0,0,0,989,994,3, - 120,60,0,990,991,5,112,0,0,991,993,3,120,60,0,992,990,1,0,0,0,993,996, - 1,0,0,0,994,992,1,0,0,0,994,995,1,0,0,0,995,119,1,0,0,0,996,994,1,0,0, - 0,997,998,3,150,75,0,998,999,5,6,0,0,999,1000,5,126,0,0,1000,1001,3,34, - 17,0,1001,1002,5,144,0,0,1002,1008,1,0,0,0,1003,1004,3,106,53,0,1004, - 1005,5,6,0,0,1005,1006,3,150,75,0,1006,1008,1,0,0,0,1007,997,1,0,0,0, - 1007,1003,1,0,0,0,1008,121,1,0,0,0,1009,1017,3,154,77,0,1010,1011,3,130, - 65,0,1011,1012,5,116,0,0,1012,1014,1,0,0,0,1013,1010,1,0,0,0,1013,1014, - 1,0,0,0,1014,1015,1,0,0,0,1015,1017,3,124,62,0,1016,1009,1,0,0,0,1016, - 1013,1,0,0,0,1017,123,1,0,0,0,1018,1023,3,150,75,0,1019,1020,5,116,0, - 0,1020,1022,3,150,75,0,1021,1019,1,0,0,0,1022,1025,1,0,0,0,1023,1021, - 1,0,0,0,1023,1024,1,0,0,0,1024,125,1,0,0,0,1025,1023,1,0,0,0,1026,1027, - 6,63,-1,0,1027,1036,3,130,65,0,1028,1036,3,128,64,0,1029,1030,5,126,0, - 0,1030,1031,3,34,17,0,1031,1032,5,144,0,0,1032,1036,1,0,0,0,1033,1036, - 3,114,57,0,1034,1036,3,154,77,0,1035,1026,1,0,0,0,1035,1028,1,0,0,0,1035, - 1029,1,0,0,0,1035,1033,1,0,0,0,1035,1034,1,0,0,0,1036,1045,1,0,0,0,1037, - 1041,10,3,0,0,1038,1042,3,148,74,0,1039,1040,5,6,0,0,1040,1042,3,150, - 75,0,1041,1038,1,0,0,0,1041,1039,1,0,0,0,1042,1044,1,0,0,0,1043,1037, - 1,0,0,0,1044,1047,1,0,0,0,1045,1043,1,0,0,0,1045,1046,1,0,0,0,1046,127, - 1,0,0,0,1047,1045,1,0,0,0,1048,1049,3,150,75,0,1049,1051,5,126,0,0,1050, - 1052,3,132,66,0,1051,1050,1,0,0,0,1051,1052,1,0,0,0,1052,1053,1,0,0,0, - 1053,1054,5,144,0,0,1054,129,1,0,0,0,1055,1056,3,134,67,0,1056,1057,5, - 116,0,0,1057,1059,1,0,0,0,1058,1055,1,0,0,0,1058,1059,1,0,0,0,1059,1060, - 1,0,0,0,1060,1061,3,150,75,0,1061,131,1,0,0,0,1062,1067,3,106,53,0,1063, - 1064,5,112,0,0,1064,1066,3,106,53,0,1065,1063,1,0,0,0,1066,1069,1,0,0, - 0,1067,1065,1,0,0,0,1067,1068,1,0,0,0,1068,133,1,0,0,0,1069,1067,1,0, - 0,0,1070,1071,3,150,75,0,1071,135,1,0,0,0,1072,1081,5,102,0,0,1073,1074, - 5,116,0,0,1074,1081,7,11,0,0,1075,1076,5,104,0,0,1076,1078,5,116,0,0, - 1077,1079,7,11,0,0,1078,1077,1,0,0,0,1078,1079,1,0,0,0,1079,1081,1,0, - 0,0,1080,1072,1,0,0,0,1080,1073,1,0,0,0,1080,1075,1,0,0,0,1081,137,1, - 0,0,0,1082,1084,7,12,0,0,1083,1082,1,0,0,0,1083,1084,1,0,0,0,1084,1091, - 1,0,0,0,1085,1092,3,136,68,0,1086,1092,5,103,0,0,1087,1092,5,104,0,0, - 1088,1092,5,105,0,0,1089,1092,5,41,0,0,1090,1092,5,55,0,0,1091,1085,1, - 0,0,0,1091,1086,1,0,0,0,1091,1087,1,0,0,0,1091,1088,1,0,0,0,1091,1089, - 1,0,0,0,1091,1090,1,0,0,0,1092,139,1,0,0,0,1093,1097,3,138,69,0,1094, - 1097,5,106,0,0,1095,1097,5,57,0,0,1096,1093,1,0,0,0,1096,1094,1,0,0,0, - 1096,1095,1,0,0,0,1097,141,1,0,0,0,1098,1099,7,13,0,0,1099,143,1,0,0, - 0,1100,1101,7,14,0,0,1101,145,1,0,0,0,1102,1103,7,15,0,0,1103,147,1,0, - 0,0,1104,1107,5,101,0,0,1105,1107,3,146,73,0,1106,1104,1,0,0,0,1106,1105, - 1,0,0,0,1107,149,1,0,0,0,1108,1112,5,101,0,0,1109,1112,3,142,71,0,1110, - 1112,3,144,72,0,1111,1108,1,0,0,0,1111,1109,1,0,0,0,1111,1110,1,0,0,0, - 1112,151,1,0,0,0,1113,1114,3,156,78,0,1114,1115,5,118,0,0,1115,1116,3, - 138,69,0,1116,153,1,0,0,0,1117,1118,5,124,0,0,1118,1119,3,150,75,0,1119, - 1120,5,142,0,0,1120,155,1,0,0,0,1121,1124,5,106,0,0,1122,1124,3,158,79, - 0,1123,1121,1,0,0,0,1123,1122,1,0,0,0,1124,157,1,0,0,0,1125,1129,5,137, - 0,0,1126,1128,3,160,80,0,1127,1126,1,0,0,0,1128,1131,1,0,0,0,1129,1127, - 1,0,0,0,1129,1130,1,0,0,0,1130,1132,1,0,0,0,1131,1129,1,0,0,0,1132,1133, - 5,139,0,0,1133,159,1,0,0,0,1134,1135,5,152,0,0,1135,1136,3,106,53,0,1136, - 1137,5,142,0,0,1137,1140,1,0,0,0,1138,1140,5,151,0,0,1139,1134,1,0,0, - 0,1139,1138,1,0,0,0,1140,161,1,0,0,0,1141,1145,5,138,0,0,1142,1144,3, - 164,82,0,1143,1142,1,0,0,0,1144,1147,1,0,0,0,1145,1143,1,0,0,0,1145,1146, - 1,0,0,0,1146,1148,1,0,0,0,1147,1145,1,0,0,0,1148,1149,5,0,0,1,1149,163, - 1,0,0,0,1150,1151,5,154,0,0,1151,1152,3,106,53,0,1152,1153,5,142,0,0, - 1153,1156,1,0,0,0,1154,1156,5,153,0,0,1155,1150,1,0,0,0,1155,1154,1,0, - 0,0,1156,165,1,0,0,0,135,169,176,185,200,212,224,240,251,265,271,281, + 0,741,742,1,0,0,0,742,743,5,144,0,0,743,752,1,0,0,0,744,746,5,126,0,0, + 745,747,5,23,0,0,746,745,1,0,0,0,746,747,1,0,0,0,747,749,1,0,0,0,748, + 750,3,108,54,0,749,748,1,0,0,0,749,750,1,0,0,0,750,751,1,0,0,0,751,753, + 5,144,0,0,752,744,1,0,0,0,752,753,1,0,0,0,753,754,1,0,0,0,754,755,5,64, + 0,0,755,756,5,126,0,0,756,757,3,88,44,0,757,758,5,144,0,0,758,833,1,0, + 0,0,759,760,3,150,75,0,760,762,5,126,0,0,761,763,3,104,52,0,762,761,1, + 0,0,0,762,763,1,0,0,0,763,764,1,0,0,0,764,765,5,144,0,0,765,774,1,0,0, + 0,766,768,5,126,0,0,767,769,5,23,0,0,768,767,1,0,0,0,768,769,1,0,0,0, + 769,771,1,0,0,0,770,772,3,108,54,0,771,770,1,0,0,0,771,772,1,0,0,0,772, + 773,1,0,0,0,773,775,5,144,0,0,774,766,1,0,0,0,774,775,1,0,0,0,775,776, + 1,0,0,0,776,777,5,64,0,0,777,778,3,150,75,0,778,833,1,0,0,0,779,785,3, + 150,75,0,780,782,5,126,0,0,781,783,3,104,52,0,782,781,1,0,0,0,782,783, + 1,0,0,0,783,784,1,0,0,0,784,786,5,144,0,0,785,780,1,0,0,0,785,786,1,0, + 0,0,786,787,1,0,0,0,787,789,5,126,0,0,788,790,5,23,0,0,789,788,1,0,0, + 0,789,790,1,0,0,0,790,792,1,0,0,0,791,793,3,108,54,0,792,791,1,0,0,0, + 792,793,1,0,0,0,793,794,1,0,0,0,794,795,5,144,0,0,795,833,1,0,0,0,796, + 833,3,114,57,0,797,833,3,158,79,0,798,833,3,140,70,0,799,800,5,114,0, + 0,800,833,3,106,53,19,801,802,5,56,0,0,802,833,3,106,53,13,803,804,3, + 130,65,0,804,805,5,116,0,0,805,807,1,0,0,0,806,803,1,0,0,0,806,807,1, + 0,0,0,807,808,1,0,0,0,808,833,5,108,0,0,809,810,5,126,0,0,810,811,3,34, + 17,0,811,812,5,144,0,0,812,833,1,0,0,0,813,814,5,126,0,0,814,815,3,106, + 53,0,815,816,5,144,0,0,816,833,1,0,0,0,817,818,5,126,0,0,818,819,3,104, + 52,0,819,820,5,144,0,0,820,833,1,0,0,0,821,823,5,125,0,0,822,824,3,104, + 52,0,823,822,1,0,0,0,823,824,1,0,0,0,824,825,1,0,0,0,825,833,5,143,0, + 0,826,828,5,124,0,0,827,829,3,30,15,0,828,827,1,0,0,0,828,829,1,0,0,0, + 829,830,1,0,0,0,830,833,5,142,0,0,831,833,3,122,61,0,832,683,1,0,0,0, + 832,703,1,0,0,0,832,710,1,0,0,0,832,712,1,0,0,0,832,716,1,0,0,0,832,727, + 1,0,0,0,832,729,1,0,0,0,832,737,1,0,0,0,832,759,1,0,0,0,832,779,1,0,0, + 0,832,796,1,0,0,0,832,797,1,0,0,0,832,798,1,0,0,0,832,799,1,0,0,0,832, + 801,1,0,0,0,832,806,1,0,0,0,832,809,1,0,0,0,832,813,1,0,0,0,832,817,1, + 0,0,0,832,821,1,0,0,0,832,826,1,0,0,0,832,831,1,0,0,0,833,927,1,0,0,0, + 834,838,10,18,0,0,835,839,5,108,0,0,836,839,5,146,0,0,837,839,5,133,0, + 0,838,835,1,0,0,0,838,836,1,0,0,0,838,837,1,0,0,0,839,840,1,0,0,0,840, + 926,3,106,53,19,841,845,10,17,0,0,842,846,5,134,0,0,843,846,5,114,0,0, + 844,846,5,113,0,0,845,842,1,0,0,0,845,843,1,0,0,0,845,844,1,0,0,0,846, + 847,1,0,0,0,847,926,3,106,53,18,848,873,10,16,0,0,849,874,5,117,0,0,850, + 874,5,118,0,0,851,874,5,129,0,0,852,874,5,127,0,0,853,874,5,128,0,0,854, + 874,5,119,0,0,855,874,5,120,0,0,856,858,5,56,0,0,857,856,1,0,0,0,857, + 858,1,0,0,0,858,859,1,0,0,0,859,861,5,40,0,0,860,862,5,14,0,0,861,860, + 1,0,0,0,861,862,1,0,0,0,862,874,1,0,0,0,863,865,5,56,0,0,864,863,1,0, + 0,0,864,865,1,0,0,0,865,866,1,0,0,0,866,874,7,10,0,0,867,874,5,140,0, + 0,868,874,5,141,0,0,869,874,5,131,0,0,870,874,5,122,0,0,871,874,5,123, + 0,0,872,874,5,130,0,0,873,849,1,0,0,0,873,850,1,0,0,0,873,851,1,0,0,0, + 873,852,1,0,0,0,873,853,1,0,0,0,873,854,1,0,0,0,873,855,1,0,0,0,873,857, + 1,0,0,0,873,864,1,0,0,0,873,867,1,0,0,0,873,868,1,0,0,0,873,869,1,0,0, + 0,873,870,1,0,0,0,873,871,1,0,0,0,873,872,1,0,0,0,874,875,1,0,0,0,875, + 926,3,106,53,17,876,877,10,14,0,0,877,878,5,132,0,0,878,926,3,106,53, + 15,879,880,10,12,0,0,880,881,5,2,0,0,881,926,3,106,53,13,882,883,10,11, + 0,0,883,884,5,61,0,0,884,926,3,106,53,12,885,887,10,10,0,0,886,888,5, + 56,0,0,887,886,1,0,0,0,887,888,1,0,0,0,888,889,1,0,0,0,889,890,5,9,0, + 0,890,891,3,106,53,0,891,892,5,2,0,0,892,893,3,106,53,11,893,926,1,0, + 0,0,894,895,10,9,0,0,895,896,5,135,0,0,896,897,3,106,53,0,897,898,5,111, + 0,0,898,899,3,106,53,9,899,926,1,0,0,0,900,901,10,22,0,0,901,902,5,125, + 0,0,902,903,3,106,53,0,903,904,5,143,0,0,904,926,1,0,0,0,905,906,10,21, + 0,0,906,907,5,116,0,0,907,926,5,104,0,0,908,909,10,20,0,0,909,910,5,116, + 0,0,910,926,3,150,75,0,911,912,10,15,0,0,912,914,5,44,0,0,913,915,5,56, + 0,0,914,913,1,0,0,0,914,915,1,0,0,0,915,916,1,0,0,0,916,926,5,57,0,0, + 917,923,10,8,0,0,918,924,3,148,74,0,919,920,5,6,0,0,920,924,3,150,75, + 0,921,922,5,6,0,0,922,924,5,106,0,0,923,918,1,0,0,0,923,919,1,0,0,0,923, + 921,1,0,0,0,924,926,1,0,0,0,925,834,1,0,0,0,925,841,1,0,0,0,925,848,1, + 0,0,0,925,876,1,0,0,0,925,879,1,0,0,0,925,882,1,0,0,0,925,885,1,0,0,0, + 925,894,1,0,0,0,925,900,1,0,0,0,925,905,1,0,0,0,925,908,1,0,0,0,925,911, + 1,0,0,0,925,917,1,0,0,0,926,929,1,0,0,0,927,925,1,0,0,0,927,928,1,0,0, + 0,928,107,1,0,0,0,929,927,1,0,0,0,930,935,3,110,55,0,931,932,5,112,0, + 0,932,934,3,110,55,0,933,931,1,0,0,0,934,937,1,0,0,0,935,933,1,0,0,0, + 935,936,1,0,0,0,936,109,1,0,0,0,937,935,1,0,0,0,938,941,3,112,56,0,939, + 941,3,106,53,0,940,938,1,0,0,0,940,939,1,0,0,0,941,111,1,0,0,0,942,943, + 5,126,0,0,943,948,3,150,75,0,944,945,5,112,0,0,945,947,3,150,75,0,946, + 944,1,0,0,0,947,950,1,0,0,0,948,946,1,0,0,0,948,949,1,0,0,0,949,951,1, + 0,0,0,950,948,1,0,0,0,951,952,5,144,0,0,952,962,1,0,0,0,953,958,3,150, + 75,0,954,955,5,112,0,0,955,957,3,150,75,0,956,954,1,0,0,0,957,960,1,0, + 0,0,958,956,1,0,0,0,958,959,1,0,0,0,959,962,1,0,0,0,960,958,1,0,0,0,961, + 942,1,0,0,0,961,953,1,0,0,0,962,963,1,0,0,0,963,964,5,107,0,0,964,965, + 3,106,53,0,965,113,1,0,0,0,966,967,5,128,0,0,967,971,3,150,75,0,968,970, + 3,116,58,0,969,968,1,0,0,0,970,973,1,0,0,0,971,969,1,0,0,0,971,972,1, + 0,0,0,972,974,1,0,0,0,973,971,1,0,0,0,974,975,5,146,0,0,975,976,5,120, + 0,0,976,995,1,0,0,0,977,978,5,128,0,0,978,982,3,150,75,0,979,981,3,116, + 58,0,980,979,1,0,0,0,981,984,1,0,0,0,982,980,1,0,0,0,982,983,1,0,0,0, + 983,985,1,0,0,0,984,982,1,0,0,0,985,987,5,120,0,0,986,988,3,114,57,0, + 987,986,1,0,0,0,987,988,1,0,0,0,988,989,1,0,0,0,989,990,5,128,0,0,990, + 991,5,146,0,0,991,992,3,150,75,0,992,993,5,120,0,0,993,995,1,0,0,0,994, + 966,1,0,0,0,994,977,1,0,0,0,995,115,1,0,0,0,996,997,3,150,75,0,997,998, + 5,118,0,0,998,999,3,156,78,0,999,1008,1,0,0,0,1000,1001,3,150,75,0,1001, + 1002,5,118,0,0,1002,1003,5,124,0,0,1003,1004,3,106,53,0,1004,1005,5,142, + 0,0,1005,1008,1,0,0,0,1006,1008,3,150,75,0,1007,996,1,0,0,0,1007,1000, + 1,0,0,0,1007,1006,1,0,0,0,1008,117,1,0,0,0,1009,1014,3,120,60,0,1010, + 1011,5,112,0,0,1011,1013,3,120,60,0,1012,1010,1,0,0,0,1013,1016,1,0,0, + 0,1014,1012,1,0,0,0,1014,1015,1,0,0,0,1015,119,1,0,0,0,1016,1014,1,0, + 0,0,1017,1018,3,150,75,0,1018,1019,5,6,0,0,1019,1020,5,126,0,0,1020,1021, + 3,34,17,0,1021,1022,5,144,0,0,1022,1028,1,0,0,0,1023,1024,3,106,53,0, + 1024,1025,5,6,0,0,1025,1026,3,150,75,0,1026,1028,1,0,0,0,1027,1017,1, + 0,0,0,1027,1023,1,0,0,0,1028,121,1,0,0,0,1029,1037,3,154,77,0,1030,1031, + 3,130,65,0,1031,1032,5,116,0,0,1032,1034,1,0,0,0,1033,1030,1,0,0,0,1033, + 1034,1,0,0,0,1034,1035,1,0,0,0,1035,1037,3,124,62,0,1036,1029,1,0,0,0, + 1036,1033,1,0,0,0,1037,123,1,0,0,0,1038,1043,3,150,75,0,1039,1040,5,116, + 0,0,1040,1042,3,150,75,0,1041,1039,1,0,0,0,1042,1045,1,0,0,0,1043,1041, + 1,0,0,0,1043,1044,1,0,0,0,1044,125,1,0,0,0,1045,1043,1,0,0,0,1046,1047, + 6,63,-1,0,1047,1056,3,130,65,0,1048,1056,3,128,64,0,1049,1050,5,126,0, + 0,1050,1051,3,34,17,0,1051,1052,5,144,0,0,1052,1056,1,0,0,0,1053,1056, + 3,114,57,0,1054,1056,3,154,77,0,1055,1046,1,0,0,0,1055,1048,1,0,0,0,1055, + 1049,1,0,0,0,1055,1053,1,0,0,0,1055,1054,1,0,0,0,1056,1065,1,0,0,0,1057, + 1061,10,3,0,0,1058,1062,3,148,74,0,1059,1060,5,6,0,0,1060,1062,3,150, + 75,0,1061,1058,1,0,0,0,1061,1059,1,0,0,0,1062,1064,1,0,0,0,1063,1057, + 1,0,0,0,1064,1067,1,0,0,0,1065,1063,1,0,0,0,1065,1066,1,0,0,0,1066,127, + 1,0,0,0,1067,1065,1,0,0,0,1068,1069,3,150,75,0,1069,1071,5,126,0,0,1070, + 1072,3,132,66,0,1071,1070,1,0,0,0,1071,1072,1,0,0,0,1072,1073,1,0,0,0, + 1073,1074,5,144,0,0,1074,129,1,0,0,0,1075,1076,3,134,67,0,1076,1077,5, + 116,0,0,1077,1079,1,0,0,0,1078,1075,1,0,0,0,1078,1079,1,0,0,0,1079,1080, + 1,0,0,0,1080,1081,3,150,75,0,1081,131,1,0,0,0,1082,1087,3,106,53,0,1083, + 1084,5,112,0,0,1084,1086,3,106,53,0,1085,1083,1,0,0,0,1086,1089,1,0,0, + 0,1087,1085,1,0,0,0,1087,1088,1,0,0,0,1088,133,1,0,0,0,1089,1087,1,0, + 0,0,1090,1091,3,150,75,0,1091,135,1,0,0,0,1092,1101,5,102,0,0,1093,1094, + 5,116,0,0,1094,1101,7,11,0,0,1095,1096,5,104,0,0,1096,1098,5,116,0,0, + 1097,1099,7,11,0,0,1098,1097,1,0,0,0,1098,1099,1,0,0,0,1099,1101,1,0, + 0,0,1100,1092,1,0,0,0,1100,1093,1,0,0,0,1100,1095,1,0,0,0,1101,137,1, + 0,0,0,1102,1104,7,12,0,0,1103,1102,1,0,0,0,1103,1104,1,0,0,0,1104,1111, + 1,0,0,0,1105,1112,3,136,68,0,1106,1112,5,103,0,0,1107,1112,5,104,0,0, + 1108,1112,5,105,0,0,1109,1112,5,41,0,0,1110,1112,5,55,0,0,1111,1105,1, + 0,0,0,1111,1106,1,0,0,0,1111,1107,1,0,0,0,1111,1108,1,0,0,0,1111,1109, + 1,0,0,0,1111,1110,1,0,0,0,1112,139,1,0,0,0,1113,1117,3,138,69,0,1114, + 1117,5,106,0,0,1115,1117,5,57,0,0,1116,1113,1,0,0,0,1116,1114,1,0,0,0, + 1116,1115,1,0,0,0,1117,141,1,0,0,0,1118,1119,7,13,0,0,1119,143,1,0,0, + 0,1120,1121,7,14,0,0,1121,145,1,0,0,0,1122,1123,7,15,0,0,1123,147,1,0, + 0,0,1124,1127,5,101,0,0,1125,1127,3,146,73,0,1126,1124,1,0,0,0,1126,1125, + 1,0,0,0,1127,149,1,0,0,0,1128,1132,5,101,0,0,1129,1132,3,142,71,0,1130, + 1132,3,144,72,0,1131,1128,1,0,0,0,1131,1129,1,0,0,0,1131,1130,1,0,0,0, + 1132,151,1,0,0,0,1133,1134,3,156,78,0,1134,1135,5,118,0,0,1135,1136,3, + 138,69,0,1136,153,1,0,0,0,1137,1138,5,124,0,0,1138,1139,3,150,75,0,1139, + 1140,5,142,0,0,1140,155,1,0,0,0,1141,1144,5,106,0,0,1142,1144,3,158,79, + 0,1143,1141,1,0,0,0,1143,1142,1,0,0,0,1144,157,1,0,0,0,1145,1149,5,137, + 0,0,1146,1148,3,160,80,0,1147,1146,1,0,0,0,1148,1151,1,0,0,0,1149,1147, + 1,0,0,0,1149,1150,1,0,0,0,1150,1152,1,0,0,0,1151,1149,1,0,0,0,1152,1153, + 5,139,0,0,1153,159,1,0,0,0,1154,1155,5,152,0,0,1155,1156,3,106,53,0,1156, + 1157,5,142,0,0,1157,1160,1,0,0,0,1158,1160,5,151,0,0,1159,1154,1,0,0, + 0,1159,1158,1,0,0,0,1160,161,1,0,0,0,1161,1165,5,138,0,0,1162,1164,3, + 164,82,0,1163,1162,1,0,0,0,1164,1167,1,0,0,0,1165,1163,1,0,0,0,1165,1166, + 1,0,0,0,1166,1168,1,0,0,0,1167,1165,1,0,0,0,1168,1169,5,0,0,1,1169,163, + 1,0,0,0,1170,1171,5,154,0,0,1171,1172,3,106,53,0,1172,1173,5,142,0,0, + 1173,1176,1,0,0,0,1174,1176,5,153,0,0,1175,1170,1,0,0,0,1175,1174,1,0, + 0,0,1176,165,1,0,0,0,141,169,176,185,200,212,224,240,251,265,271,281, 290,293,297,300,304,307,310,313,316,320,324,327,330,333,337,340,349,355, 376,393,410,416,422,433,435,446,449,455,463,469,471,475,480,483,486,490, 494,497,499,502,506,510,513,515,517,522,533,539,546,551,555,559,565,567, - 574,582,585,588,607,621,637,649,661,669,673,680,686,695,699,723,740,752, - 762,765,769,772,786,803,808,812,818,825,837,841,844,853,867,894,903,905, - 907,915,920,928,938,941,951,962,967,974,987,994,1007,1013,1016,1023,1035, - 1041,1045,1051,1058,1067,1078,1080,1083,1091,1096,1106,1111,1123,1129, - 1139,1145,1155 + 574,582,585,588,607,621,637,649,661,669,673,680,686,695,699,723,740,746, + 749,752,762,768,771,774,782,785,789,792,806,823,828,832,838,845,857,861, + 864,873,887,914,923,925,927,935,940,948,958,961,971,982,987,994,1007, + 1014,1027,1033,1036,1043,1055,1061,1065,1071,1078,1087,1098,1100,1103, + 1111,1116,1126,1131,1143,1149,1159,1165,1175 }; staticData->serializedATN = antlr4::atn::SerializedATNView(serializedATNSegment, sizeof(serializedATNSegment) / sizeof(serializedATNSegment[0])); @@ -6335,18 +6344,34 @@ tree::TerminalNode* HogQLParser::ColumnExprWinFunctionTargetContext::OVER() { return getToken(HogQLParser::OVER, 0); } -tree::TerminalNode* HogQLParser::ColumnExprWinFunctionTargetContext::LPAREN() { - return getToken(HogQLParser::LPAREN, 0); +std::vector HogQLParser::ColumnExprWinFunctionTargetContext::LPAREN() { + return getTokens(HogQLParser::LPAREN); } -tree::TerminalNode* HogQLParser::ColumnExprWinFunctionTargetContext::RPAREN() { - return getToken(HogQLParser::RPAREN, 0); +tree::TerminalNode* HogQLParser::ColumnExprWinFunctionTargetContext::LPAREN(size_t i) { + return getToken(HogQLParser::LPAREN, i); +} + +std::vector HogQLParser::ColumnExprWinFunctionTargetContext::RPAREN() { + return getTokens(HogQLParser::RPAREN); +} + +tree::TerminalNode* HogQLParser::ColumnExprWinFunctionTargetContext::RPAREN(size_t i) { + return getToken(HogQLParser::RPAREN, i); } HogQLParser::ColumnExprListContext* HogQLParser::ColumnExprWinFunctionTargetContext::columnExprList() { return getRuleContext(0); } +tree::TerminalNode* HogQLParser::ColumnExprWinFunctionTargetContext::DISTINCT() { + return getToken(HogQLParser::DISTINCT, 0); +} + +HogQLParser::ColumnArgListContext* HogQLParser::ColumnExprWinFunctionTargetContext::columnArgList() { + return getRuleContext(0); +} + HogQLParser::ColumnExprWinFunctionTargetContext::ColumnExprWinFunctionTargetContext(ColumnExprContext *ctx) { copyFrom(ctx); } @@ -6767,6 +6792,14 @@ HogQLParser::ColumnExprListContext* HogQLParser::ColumnExprWinFunctionContext::c return getRuleContext(0); } +tree::TerminalNode* HogQLParser::ColumnExprWinFunctionContext::DISTINCT() { + return getToken(HogQLParser::DISTINCT, 0); +} + +HogQLParser::ColumnArgListContext* HogQLParser::ColumnExprWinFunctionContext::columnArgList() { + return getRuleContext(0); +} + HogQLParser::ColumnExprWinFunctionContext::ColumnExprWinFunctionContext(ColumnExprContext *ctx) { copyFrom(ctx); } @@ -6883,9 +6916,9 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { try { size_t alt; enterOuterAlt(_localctx, 1); - setState(812); + setState(832); _errHandler->sync(this); - switch (getInterpreter()->adaptivePredict(_input, 90, _ctx)) { + switch (getInterpreter()->adaptivePredict(_input, 96, _ctx)) { case 1: { _localctx = _tracker.createInstance(_localctx); _ctx = _localctx; @@ -7072,13 +7105,47 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { } setState(742); match(HogQLParser::RPAREN); - setState(744); + setState(752); + _errHandler->sync(this); + + _la = _input->LA(1); + if (_la == HogQLParser::LPAREN) { + setState(744); + match(HogQLParser::LPAREN); + setState(746); + _errHandler->sync(this); + + switch (getInterpreter()->adaptivePredict(_input, 82, _ctx)) { + case 1: { + setState(745); + match(HogQLParser::DISTINCT); + break; + } + + default: + break; + } + setState(749); + _errHandler->sync(this); + + _la = _input->LA(1); + if ((((_la & ~ 0x3fULL) == 0) && + ((1ULL << _la) & -1125900443713538) != 0) || ((((_la - 64) & ~ 0x3fULL) == 0) && + ((1ULL << (_la - 64)) & 8076106347046764543) != 0) || ((((_la - 128) & ~ 0x3fULL) == 0) && + ((1ULL << (_la - 128)) & 577) != 0)) { + setState(748); + columnArgList(); + } + setState(751); + match(HogQLParser::RPAREN); + } + setState(754); match(HogQLParser::OVER); - setState(745); + setState(755); match(HogQLParser::LPAREN); - setState(746); + setState(756); windowExpr(); - setState(747); + setState(757); match(HogQLParser::RPAREN); break; } @@ -7087,12 +7154,12 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { _localctx = _tracker.createInstance(_localctx); _ctx = _localctx; previousContext = _localctx; - setState(749); + setState(759); identifier(); - setState(750); + setState(760); match(HogQLParser::LPAREN); - setState(752); + setState(762); _errHandler->sync(this); _la = _input->LA(1); @@ -7100,14 +7167,48 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { ((1ULL << _la) & -1125900443713538) != 0) || ((((_la - 64) & ~ 0x3fULL) == 0) && ((1ULL << (_la - 64)) & 8076106347046764543) != 0) || ((((_la - 128) & ~ 0x3fULL) == 0) && ((1ULL << (_la - 128)) & 577) != 0)) { - setState(751); + setState(761); columnExprList(); } - setState(754); + setState(764); match(HogQLParser::RPAREN); - setState(756); + setState(774); + _errHandler->sync(this); + + _la = _input->LA(1); + if (_la == HogQLParser::LPAREN) { + setState(766); + match(HogQLParser::LPAREN); + setState(768); + _errHandler->sync(this); + + switch (getInterpreter()->adaptivePredict(_input, 86, _ctx)) { + case 1: { + setState(767); + match(HogQLParser::DISTINCT); + break; + } + + default: + break; + } + setState(771); + _errHandler->sync(this); + + _la = _input->LA(1); + if ((((_la & ~ 0x3fULL) == 0) && + ((1ULL << _la) & -1125900443713538) != 0) || ((((_la - 64) & ~ 0x3fULL) == 0) && + ((1ULL << (_la - 64)) & 8076106347046764543) != 0) || ((((_la - 128) & ~ 0x3fULL) == 0) && + ((1ULL << (_la - 128)) & 577) != 0)) { + setState(770); + columnArgList(); + } + setState(773); + match(HogQLParser::RPAREN); + } + setState(776); match(HogQLParser::OVER); - setState(757); + setState(777); identifier(); break; } @@ -7116,16 +7217,16 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { _localctx = _tracker.createInstance(_localctx); _ctx = _localctx; previousContext = _localctx; - setState(759); + setState(779); identifier(); - setState(765); + setState(785); _errHandler->sync(this); - switch (getInterpreter()->adaptivePredict(_input, 84, _ctx)) { + switch (getInterpreter()->adaptivePredict(_input, 90, _ctx)) { case 1: { - setState(760); + setState(780); match(HogQLParser::LPAREN); - setState(762); + setState(782); _errHandler->sync(this); _la = _input->LA(1); @@ -7133,10 +7234,10 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { ((1ULL << _la) & -1125900443713538) != 0) || ((((_la - 64) & ~ 0x3fULL) == 0) && ((1ULL << (_la - 64)) & 8076106347046764543) != 0) || ((((_la - 128) & ~ 0x3fULL) == 0) && ((1ULL << (_la - 128)) & 577) != 0)) { - setState(761); + setState(781); columnExprList(); } - setState(764); + setState(784); match(HogQLParser::RPAREN); break; } @@ -7144,14 +7245,14 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { default: break; } - setState(767); + setState(787); match(HogQLParser::LPAREN); - setState(769); + setState(789); _errHandler->sync(this); - switch (getInterpreter()->adaptivePredict(_input, 85, _ctx)) { + switch (getInterpreter()->adaptivePredict(_input, 91, _ctx)) { case 1: { - setState(768); + setState(788); match(HogQLParser::DISTINCT); break; } @@ -7159,7 +7260,7 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { default: break; } - setState(772); + setState(792); _errHandler->sync(this); _la = _input->LA(1); @@ -7167,10 +7268,10 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { ((1ULL << _la) & -1125900443713538) != 0) || ((((_la - 64) & ~ 0x3fULL) == 0) && ((1ULL << (_la - 64)) & 8076106347046764543) != 0) || ((((_la - 128) & ~ 0x3fULL) == 0) && ((1ULL << (_la - 128)) & 577) != 0)) { - setState(771); + setState(791); columnArgList(); } - setState(774); + setState(794); match(HogQLParser::RPAREN); break; } @@ -7179,7 +7280,7 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { _localctx = _tracker.createInstance(_localctx); _ctx = _localctx; previousContext = _localctx; - setState(776); + setState(796); hogqlxTagElement(); break; } @@ -7188,7 +7289,7 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { _localctx = _tracker.createInstance(_localctx); _ctx = _localctx; previousContext = _localctx; - setState(777); + setState(797); templateString(); break; } @@ -7197,7 +7298,7 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { _localctx = _tracker.createInstance(_localctx); _ctx = _localctx; previousContext = _localctx; - setState(778); + setState(798); literal(); break; } @@ -7206,9 +7307,9 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { _localctx = _tracker.createInstance(_localctx); _ctx = _localctx; previousContext = _localctx; - setState(779); + setState(799); match(HogQLParser::DASH); - setState(780); + setState(800); columnExpr(19); break; } @@ -7217,9 +7318,9 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { _localctx = _tracker.createInstance(_localctx); _ctx = _localctx; previousContext = _localctx; - setState(781); + setState(801); match(HogQLParser::NOT); - setState(782); + setState(802); columnExpr(13); break; } @@ -7228,19 +7329,19 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { _localctx = _tracker.createInstance(_localctx); _ctx = _localctx; previousContext = _localctx; - setState(786); + setState(806); _errHandler->sync(this); _la = _input->LA(1); if ((((_la & ~ 0x3fULL) == 0) && ((1ULL << _la) & -181272084561788930) != 0) || ((((_la - 64) & ~ 0x3fULL) == 0) && ((1ULL << (_la - 64)) & 201863462911) != 0)) { - setState(783); + setState(803); tableIdentifier(); - setState(784); + setState(804); match(HogQLParser::DOT); } - setState(788); + setState(808); match(HogQLParser::ASTERISK); break; } @@ -7249,11 +7350,11 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { _localctx = _tracker.createInstance(_localctx); _ctx = _localctx; previousContext = _localctx; - setState(789); + setState(809); match(HogQLParser::LPAREN); - setState(790); + setState(810); selectUnionStmt(); - setState(791); + setState(811); match(HogQLParser::RPAREN); break; } @@ -7262,11 +7363,11 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { _localctx = _tracker.createInstance(_localctx); _ctx = _localctx; previousContext = _localctx; - setState(793); + setState(813); match(HogQLParser::LPAREN); - setState(794); + setState(814); columnExpr(0); - setState(795); + setState(815); match(HogQLParser::RPAREN); break; } @@ -7275,11 +7376,11 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { _localctx = _tracker.createInstance(_localctx); _ctx = _localctx; previousContext = _localctx; - setState(797); + setState(817); match(HogQLParser::LPAREN); - setState(798); + setState(818); columnExprList(); - setState(799); + setState(819); match(HogQLParser::RPAREN); break; } @@ -7288,9 +7389,9 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { _localctx = _tracker.createInstance(_localctx); _ctx = _localctx; previousContext = _localctx; - setState(801); + setState(821); match(HogQLParser::LBRACKET); - setState(803); + setState(823); _errHandler->sync(this); _la = _input->LA(1); @@ -7298,10 +7399,10 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { ((1ULL << _la) & -1125900443713538) != 0) || ((((_la - 64) & ~ 0x3fULL) == 0) && ((1ULL << (_la - 64)) & 8076106347046764543) != 0) || ((((_la - 128) & ~ 0x3fULL) == 0) && ((1ULL << (_la - 128)) & 577) != 0)) { - setState(802); + setState(822); columnExprList(); } - setState(805); + setState(825); match(HogQLParser::RBRACKET); break; } @@ -7310,9 +7411,9 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { _localctx = _tracker.createInstance(_localctx); _ctx = _localctx; previousContext = _localctx; - setState(806); + setState(826); match(HogQLParser::LBRACE); - setState(808); + setState(828); _errHandler->sync(this); _la = _input->LA(1); @@ -7320,10 +7421,10 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { ((1ULL << _la) & -1125900443713538) != 0) || ((((_la - 64) & ~ 0x3fULL) == 0) && ((1ULL << (_la - 64)) & 8076106347046764543) != 0) || ((((_la - 128) & ~ 0x3fULL) == 0) && ((1ULL << (_la - 128)) & 577) != 0)) { - setState(807); + setState(827); kvPairList(); } - setState(810); + setState(830); match(HogQLParser::RBRACE); break; } @@ -7332,7 +7433,7 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { _localctx = _tracker.createInstance(_localctx); _ctx = _localctx; previousContext = _localctx; - setState(811); + setState(831); columnIdentifier(); break; } @@ -7341,42 +7442,42 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { break; } _ctx->stop = _input->LT(-1); - setState(907); + setState(927); _errHandler->sync(this); - alt = getInterpreter()->adaptivePredict(_input, 101, _ctx); + alt = getInterpreter()->adaptivePredict(_input, 107, _ctx); while (alt != 2 && alt != atn::ATN::INVALID_ALT_NUMBER) { if (alt == 1) { if (!_parseListeners.empty()) triggerExitRuleEvent(); previousContext = _localctx; - setState(905); + setState(925); _errHandler->sync(this); - switch (getInterpreter()->adaptivePredict(_input, 100, _ctx)) { + switch (getInterpreter()->adaptivePredict(_input, 106, _ctx)) { case 1: { auto newContext = _tracker.createInstance(_tracker.createInstance(parentContext, parentState)); _localctx = newContext; newContext->left = previousContext; pushNewRecursionContext(newContext, startState, RuleColumnExpr); - setState(814); + setState(834); if (!(precpred(_ctx, 18))) throw FailedPredicateException(this, "precpred(_ctx, 18)"); - setState(818); + setState(838); _errHandler->sync(this); switch (_input->LA(1)) { case HogQLParser::ASTERISK: { - setState(815); + setState(835); antlrcpp::downCast(_localctx)->operator_ = match(HogQLParser::ASTERISK); break; } case HogQLParser::SLASH: { - setState(816); + setState(836); antlrcpp::downCast(_localctx)->operator_ = match(HogQLParser::SLASH); break; } case HogQLParser::PERCENT: { - setState(817); + setState(837); antlrcpp::downCast(_localctx)->operator_ = match(HogQLParser::PERCENT); break; } @@ -7384,7 +7485,7 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { default: throw NoViableAltException(this); } - setState(820); + setState(840); antlrcpp::downCast(_localctx)->right = columnExpr(19); break; } @@ -7394,26 +7495,26 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { _localctx = newContext; newContext->left = previousContext; pushNewRecursionContext(newContext, startState, RuleColumnExpr); - setState(821); + setState(841); if (!(precpred(_ctx, 17))) throw FailedPredicateException(this, "precpred(_ctx, 17)"); - setState(825); + setState(845); _errHandler->sync(this); switch (_input->LA(1)) { case HogQLParser::PLUS: { - setState(822); + setState(842); antlrcpp::downCast(_localctx)->operator_ = match(HogQLParser::PLUS); break; } case HogQLParser::DASH: { - setState(823); + setState(843); antlrcpp::downCast(_localctx)->operator_ = match(HogQLParser::DASH); break; } case HogQLParser::CONCAT: { - setState(824); + setState(844); antlrcpp::downCast(_localctx)->operator_ = match(HogQLParser::CONCAT); break; } @@ -7421,7 +7522,7 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { default: throw NoViableAltException(this); } - setState(827); + setState(847); antlrcpp::downCast(_localctx)->right = columnExpr(18); break; } @@ -7431,71 +7532,71 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { _localctx = newContext; newContext->left = previousContext; pushNewRecursionContext(newContext, startState, RuleColumnExpr); - setState(828); + setState(848); if (!(precpred(_ctx, 16))) throw FailedPredicateException(this, "precpred(_ctx, 16)"); - setState(853); + setState(873); _errHandler->sync(this); - switch (getInterpreter()->adaptivePredict(_input, 96, _ctx)) { + switch (getInterpreter()->adaptivePredict(_input, 102, _ctx)) { case 1: { - setState(829); + setState(849); antlrcpp::downCast(_localctx)->operator_ = match(HogQLParser::EQ_DOUBLE); break; } case 2: { - setState(830); + setState(850); antlrcpp::downCast(_localctx)->operator_ = match(HogQLParser::EQ_SINGLE); break; } case 3: { - setState(831); + setState(851); antlrcpp::downCast(_localctx)->operator_ = match(HogQLParser::NOT_EQ); break; } case 4: { - setState(832); + setState(852); antlrcpp::downCast(_localctx)->operator_ = match(HogQLParser::LT_EQ); break; } case 5: { - setState(833); + setState(853); antlrcpp::downCast(_localctx)->operator_ = match(HogQLParser::LT); break; } case 6: { - setState(834); + setState(854); antlrcpp::downCast(_localctx)->operator_ = match(HogQLParser::GT_EQ); break; } case 7: { - setState(835); + setState(855); antlrcpp::downCast(_localctx)->operator_ = match(HogQLParser::GT); break; } case 8: { - setState(837); + setState(857); _errHandler->sync(this); _la = _input->LA(1); if (_la == HogQLParser::NOT) { - setState(836); + setState(856); antlrcpp::downCast(_localctx)->operator_ = match(HogQLParser::NOT); } - setState(839); + setState(859); match(HogQLParser::IN); - setState(841); + setState(861); _errHandler->sync(this); - switch (getInterpreter()->adaptivePredict(_input, 94, _ctx)) { + switch (getInterpreter()->adaptivePredict(_input, 100, _ctx)) { case 1: { - setState(840); + setState(860); match(HogQLParser::COHORT); break; } @@ -7507,15 +7608,15 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { } case 9: { - setState(844); + setState(864); _errHandler->sync(this); _la = _input->LA(1); if (_la == HogQLParser::NOT) { - setState(843); + setState(863); antlrcpp::downCast(_localctx)->operator_ = match(HogQLParser::NOT); } - setState(846); + setState(866); _la = _input->LA(1); if (!(_la == HogQLParser::ILIKE @@ -7530,37 +7631,37 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { } case 10: { - setState(847); + setState(867); antlrcpp::downCast(_localctx)->operator_ = match(HogQLParser::REGEX_SINGLE); break; } case 11: { - setState(848); + setState(868); antlrcpp::downCast(_localctx)->operator_ = match(HogQLParser::REGEX_DOUBLE); break; } case 12: { - setState(849); + setState(869); antlrcpp::downCast(_localctx)->operator_ = match(HogQLParser::NOT_REGEX); break; } case 13: { - setState(850); + setState(870); antlrcpp::downCast(_localctx)->operator_ = match(HogQLParser::IREGEX_SINGLE); break; } case 14: { - setState(851); + setState(871); antlrcpp::downCast(_localctx)->operator_ = match(HogQLParser::IREGEX_DOUBLE); break; } case 15: { - setState(852); + setState(872); antlrcpp::downCast(_localctx)->operator_ = match(HogQLParser::NOT_IREGEX); break; } @@ -7568,7 +7669,7 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { default: break; } - setState(855); + setState(875); antlrcpp::downCast(_localctx)->right = columnExpr(17); break; } @@ -7577,12 +7678,12 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { auto newContext = _tracker.createInstance(_tracker.createInstance(parentContext, parentState)); _localctx = newContext; pushNewRecursionContext(newContext, startState, RuleColumnExpr); - setState(856); + setState(876); if (!(precpred(_ctx, 14))) throw FailedPredicateException(this, "precpred(_ctx, 14)"); - setState(857); + setState(877); match(HogQLParser::NULLISH); - setState(858); + setState(878); columnExpr(15); break; } @@ -7591,12 +7692,12 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { auto newContext = _tracker.createInstance(_tracker.createInstance(parentContext, parentState)); _localctx = newContext; pushNewRecursionContext(newContext, startState, RuleColumnExpr); - setState(859); + setState(879); if (!(precpred(_ctx, 12))) throw FailedPredicateException(this, "precpred(_ctx, 12)"); - setState(860); + setState(880); match(HogQLParser::AND); - setState(861); + setState(881); columnExpr(13); break; } @@ -7605,12 +7706,12 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { auto newContext = _tracker.createInstance(_tracker.createInstance(parentContext, parentState)); _localctx = newContext; pushNewRecursionContext(newContext, startState, RuleColumnExpr); - setState(862); + setState(882); if (!(precpred(_ctx, 11))) throw FailedPredicateException(this, "precpred(_ctx, 11)"); - setState(863); + setState(883); match(HogQLParser::OR); - setState(864); + setState(884); columnExpr(12); break; } @@ -7619,24 +7720,24 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { auto newContext = _tracker.createInstance(_tracker.createInstance(parentContext, parentState)); _localctx = newContext; pushNewRecursionContext(newContext, startState, RuleColumnExpr); - setState(865); + setState(885); if (!(precpred(_ctx, 10))) throw FailedPredicateException(this, "precpred(_ctx, 10)"); - setState(867); + setState(887); _errHandler->sync(this); _la = _input->LA(1); if (_la == HogQLParser::NOT) { - setState(866); + setState(886); match(HogQLParser::NOT); } - setState(869); + setState(889); match(HogQLParser::BETWEEN); - setState(870); + setState(890); columnExpr(0); - setState(871); + setState(891); match(HogQLParser::AND); - setState(872); + setState(892); columnExpr(11); break; } @@ -7645,16 +7746,16 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { auto newContext = _tracker.createInstance(_tracker.createInstance(parentContext, parentState)); _localctx = newContext; pushNewRecursionContext(newContext, startState, RuleColumnExpr); - setState(874); + setState(894); if (!(precpred(_ctx, 9))) throw FailedPredicateException(this, "precpred(_ctx, 9)"); - setState(875); + setState(895); match(HogQLParser::QUERY); - setState(876); + setState(896); columnExpr(0); - setState(877); + setState(897); match(HogQLParser::COLON); - setState(878); + setState(898); columnExpr(9); break; } @@ -7663,14 +7764,14 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { auto newContext = _tracker.createInstance(_tracker.createInstance(parentContext, parentState)); _localctx = newContext; pushNewRecursionContext(newContext, startState, RuleColumnExpr); - setState(880); + setState(900); if (!(precpred(_ctx, 22))) throw FailedPredicateException(this, "precpred(_ctx, 22)"); - setState(881); + setState(901); match(HogQLParser::LBRACKET); - setState(882); + setState(902); columnExpr(0); - setState(883); + setState(903); match(HogQLParser::RBRACKET); break; } @@ -7679,12 +7780,12 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { auto newContext = _tracker.createInstance(_tracker.createInstance(parentContext, parentState)); _localctx = newContext; pushNewRecursionContext(newContext, startState, RuleColumnExpr); - setState(885); + setState(905); if (!(precpred(_ctx, 21))) throw FailedPredicateException(this, "precpred(_ctx, 21)"); - setState(886); + setState(906); match(HogQLParser::DOT); - setState(887); + setState(907); match(HogQLParser::DECIMAL_LITERAL); break; } @@ -7693,12 +7794,12 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { auto newContext = _tracker.createInstance(_tracker.createInstance(parentContext, parentState)); _localctx = newContext; pushNewRecursionContext(newContext, startState, RuleColumnExpr); - setState(888); + setState(908); if (!(precpred(_ctx, 20))) throw FailedPredicateException(this, "precpred(_ctx, 20)"); - setState(889); + setState(909); match(HogQLParser::DOT); - setState(890); + setState(910); identifier(); break; } @@ -7707,20 +7808,20 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { auto newContext = _tracker.createInstance(_tracker.createInstance(parentContext, parentState)); _localctx = newContext; pushNewRecursionContext(newContext, startState, RuleColumnExpr); - setState(891); + setState(911); if (!(precpred(_ctx, 15))) throw FailedPredicateException(this, "precpred(_ctx, 15)"); - setState(892); + setState(912); match(HogQLParser::IS); - setState(894); + setState(914); _errHandler->sync(this); _la = _input->LA(1); if (_la == HogQLParser::NOT) { - setState(893); + setState(913); match(HogQLParser::NOT); } - setState(896); + setState(916); match(HogQLParser::NULL_SQL); break; } @@ -7729,30 +7830,30 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { auto newContext = _tracker.createInstance(_tracker.createInstance(parentContext, parentState)); _localctx = newContext; pushNewRecursionContext(newContext, startState, RuleColumnExpr); - setState(897); + setState(917); if (!(precpred(_ctx, 8))) throw FailedPredicateException(this, "precpred(_ctx, 8)"); - setState(903); + setState(923); _errHandler->sync(this); - switch (getInterpreter()->adaptivePredict(_input, 99, _ctx)) { + switch (getInterpreter()->adaptivePredict(_input, 105, _ctx)) { case 1: { - setState(898); + setState(918); alias(); break; } case 2: { - setState(899); + setState(919); match(HogQLParser::AS); - setState(900); + setState(920); identifier(); break; } case 3: { - setState(901); + setState(921); match(HogQLParser::AS); - setState(902); + setState(922); match(HogQLParser::STRING_LITERAL); break; } @@ -7767,9 +7868,9 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { break; } } - setState(909); + setState(929); _errHandler->sync(this); - alt = getInterpreter()->adaptivePredict(_input, 101, _ctx); + alt = getInterpreter()->adaptivePredict(_input, 107, _ctx); } } catch (RecognitionException &e) { @@ -7829,17 +7930,17 @@ HogQLParser::ColumnArgListContext* HogQLParser::columnArgList() { }); try { enterOuterAlt(_localctx, 1); - setState(910); + setState(930); columnArgExpr(); - setState(915); + setState(935); _errHandler->sync(this); _la = _input->LA(1); while (_la == HogQLParser::COMMA) { - setState(911); + setState(931); match(HogQLParser::COMMA); - setState(912); + setState(932); columnArgExpr(); - setState(917); + setState(937); _errHandler->sync(this); _la = _input->LA(1); } @@ -7893,19 +7994,19 @@ HogQLParser::ColumnArgExprContext* HogQLParser::columnArgExpr() { exitRule(); }); try { - setState(920); + setState(940); _errHandler->sync(this); - switch (getInterpreter()->adaptivePredict(_input, 103, _ctx)) { + switch (getInterpreter()->adaptivePredict(_input, 109, _ctx)) { case 1: { enterOuterAlt(_localctx, 1); - setState(918); + setState(938); columnLambdaExpr(); break; } case 2: { enterOuterAlt(_localctx, 2); - setState(919); + setState(939); columnExpr(0); break; } @@ -7989,27 +8090,27 @@ HogQLParser::ColumnLambdaExprContext* HogQLParser::columnLambdaExpr() { }); try { enterOuterAlt(_localctx, 1); - setState(941); + setState(961); _errHandler->sync(this); switch (_input->LA(1)) { case HogQLParser::LPAREN: { - setState(922); + setState(942); match(HogQLParser::LPAREN); - setState(923); + setState(943); identifier(); - setState(928); + setState(948); _errHandler->sync(this); _la = _input->LA(1); while (_la == HogQLParser::COMMA) { - setState(924); + setState(944); match(HogQLParser::COMMA); - setState(925); + setState(945); identifier(); - setState(930); + setState(950); _errHandler->sync(this); _la = _input->LA(1); } - setState(931); + setState(951); match(HogQLParser::RPAREN); break; } @@ -8108,17 +8209,17 @@ HogQLParser::ColumnLambdaExprContext* HogQLParser::columnLambdaExpr() { case HogQLParser::WITH: case HogQLParser::YEAR: case HogQLParser::IDENTIFIER: { - setState(933); + setState(953); identifier(); - setState(938); + setState(958); _errHandler->sync(this); _la = _input->LA(1); while (_la == HogQLParser::COMMA) { - setState(934); + setState(954); match(HogQLParser::COMMA); - setState(935); + setState(955); identifier(); - setState(940); + setState(960); _errHandler->sync(this); _la = _input->LA(1); } @@ -8128,9 +8229,9 @@ HogQLParser::ColumnLambdaExprContext* HogQLParser::columnLambdaExpr() { default: throw NoViableAltException(this); } - setState(943); + setState(963); match(HogQLParser::ARROW); - setState(944); + setState(964); columnExpr(0); } @@ -8257,31 +8358,31 @@ HogQLParser::HogqlxTagElementContext* HogQLParser::hogqlxTagElement() { exitRule(); }); try { - setState(974); + setState(994); _errHandler->sync(this); - switch (getInterpreter()->adaptivePredict(_input, 110, _ctx)) { + switch (getInterpreter()->adaptivePredict(_input, 116, _ctx)) { case 1: { _localctx = _tracker.createInstance(_localctx); enterOuterAlt(_localctx, 1); - setState(946); + setState(966); match(HogQLParser::LT); - setState(947); + setState(967); identifier(); - setState(951); + setState(971); _errHandler->sync(this); _la = _input->LA(1); while ((((_la & ~ 0x3fULL) == 0) && ((1ULL << _la) & -181272084561788930) != 0) || ((((_la - 64) & ~ 0x3fULL) == 0) && ((1ULL << (_la - 64)) & 201863462911) != 0)) { - setState(948); + setState(968); hogqlxTagAttribute(); - setState(953); + setState(973); _errHandler->sync(this); _la = _input->LA(1); } - setState(954); + setState(974); match(HogQLParser::SLASH); - setState(955); + setState(975); match(HogQLParser::GT); break; } @@ -8289,30 +8390,30 @@ HogQLParser::HogqlxTagElementContext* HogQLParser::hogqlxTagElement() { case 2: { _localctx = _tracker.createInstance(_localctx); enterOuterAlt(_localctx, 2); - setState(957); + setState(977); match(HogQLParser::LT); - setState(958); + setState(978); identifier(); - setState(962); + setState(982); _errHandler->sync(this); _la = _input->LA(1); while ((((_la & ~ 0x3fULL) == 0) && ((1ULL << _la) & -181272084561788930) != 0) || ((((_la - 64) & ~ 0x3fULL) == 0) && ((1ULL << (_la - 64)) & 201863462911) != 0)) { - setState(959); + setState(979); hogqlxTagAttribute(); - setState(964); + setState(984); _errHandler->sync(this); _la = _input->LA(1); } - setState(965); + setState(985); match(HogQLParser::GT); - setState(967); + setState(987); _errHandler->sync(this); - switch (getInterpreter()->adaptivePredict(_input, 109, _ctx)) { + switch (getInterpreter()->adaptivePredict(_input, 115, _ctx)) { case 1: { - setState(966); + setState(986); hogqlxTagElement(); break; } @@ -8320,13 +8421,13 @@ HogQLParser::HogqlxTagElementContext* HogQLParser::hogqlxTagElement() { default: break; } - setState(969); + setState(989); match(HogQLParser::LT); - setState(970); + setState(990); match(HogQLParser::SLASH); - setState(971); + setState(991); identifier(); - setState(972); + setState(992); match(HogQLParser::GT); break; } @@ -8400,38 +8501,38 @@ HogQLParser::HogqlxTagAttributeContext* HogQLParser::hogqlxTagAttribute() { exitRule(); }); try { - setState(987); + setState(1007); _errHandler->sync(this); - switch (getInterpreter()->adaptivePredict(_input, 111, _ctx)) { + switch (getInterpreter()->adaptivePredict(_input, 117, _ctx)) { case 1: { enterOuterAlt(_localctx, 1); - setState(976); + setState(996); identifier(); - setState(977); + setState(997); match(HogQLParser::EQ_SINGLE); - setState(978); + setState(998); string(); break; } case 2: { enterOuterAlt(_localctx, 2); - setState(980); + setState(1000); identifier(); - setState(981); + setState(1001); match(HogQLParser::EQ_SINGLE); - setState(982); + setState(1002); match(HogQLParser::LBRACE); - setState(983); + setState(1003); columnExpr(0); - setState(984); + setState(1004); match(HogQLParser::RBRACE); break; } case 3: { enterOuterAlt(_localctx, 3); - setState(986); + setState(1006); identifier(); break; } @@ -8499,17 +8600,17 @@ HogQLParser::WithExprListContext* HogQLParser::withExprList() { }); try { enterOuterAlt(_localctx, 1); - setState(989); + setState(1009); withExpr(); - setState(994); + setState(1014); _errHandler->sync(this); _la = _input->LA(1); while (_la == HogQLParser::COMMA) { - setState(990); + setState(1010); match(HogQLParser::COMMA); - setState(991); + setState(1011); withExpr(); - setState(996); + setState(1016); _errHandler->sync(this); _la = _input->LA(1); } @@ -8605,21 +8706,21 @@ HogQLParser::WithExprContext* HogQLParser::withExpr() { exitRule(); }); try { - setState(1007); + setState(1027); _errHandler->sync(this); - switch (getInterpreter()->adaptivePredict(_input, 113, _ctx)) { + switch (getInterpreter()->adaptivePredict(_input, 119, _ctx)) { case 1: { _localctx = _tracker.createInstance(_localctx); enterOuterAlt(_localctx, 1); - setState(997); + setState(1017); identifier(); - setState(998); + setState(1018); match(HogQLParser::AS); - setState(999); + setState(1019); match(HogQLParser::LPAREN); - setState(1000); + setState(1020); selectUnionStmt(); - setState(1001); + setState(1021); match(HogQLParser::RPAREN); break; } @@ -8627,11 +8728,11 @@ HogQLParser::WithExprContext* HogQLParser::withExpr() { case 2: { _localctx = _tracker.createInstance(_localctx); enterOuterAlt(_localctx, 2); - setState(1003); + setState(1023); columnExpr(0); - setState(1004); + setState(1024); match(HogQLParser::AS); - setState(1005); + setState(1025); identifier(); break; } @@ -8697,12 +8798,12 @@ HogQLParser::ColumnIdentifierContext* HogQLParser::columnIdentifier() { exitRule(); }); try { - setState(1016); + setState(1036); _errHandler->sync(this); switch (_input->LA(1)) { case HogQLParser::LBRACE: { enterOuterAlt(_localctx, 1); - setState(1009); + setState(1029); placeholder(); break; } @@ -8802,14 +8903,14 @@ HogQLParser::ColumnIdentifierContext* HogQLParser::columnIdentifier() { case HogQLParser::YEAR: case HogQLParser::IDENTIFIER: { enterOuterAlt(_localctx, 2); - setState(1013); + setState(1033); _errHandler->sync(this); - switch (getInterpreter()->adaptivePredict(_input, 114, _ctx)) { + switch (getInterpreter()->adaptivePredict(_input, 120, _ctx)) { case 1: { - setState(1010); + setState(1030); tableIdentifier(); - setState(1011); + setState(1031); match(HogQLParser::DOT); break; } @@ -8817,7 +8918,7 @@ HogQLParser::ColumnIdentifierContext* HogQLParser::columnIdentifier() { default: break; } - setState(1015); + setState(1035); nestedIdentifier(); break; } @@ -8885,21 +8986,21 @@ HogQLParser::NestedIdentifierContext* HogQLParser::nestedIdentifier() { try { size_t alt; enterOuterAlt(_localctx, 1); - setState(1018); + setState(1038); identifier(); - setState(1023); + setState(1043); _errHandler->sync(this); - alt = getInterpreter()->adaptivePredict(_input, 116, _ctx); + alt = getInterpreter()->adaptivePredict(_input, 122, _ctx); while (alt != 2 && alt != atn::ATN::INVALID_ALT_NUMBER) { if (alt == 1) { - setState(1019); + setState(1039); match(HogQLParser::DOT); - setState(1020); + setState(1040); identifier(); } - setState(1025); + setState(1045); _errHandler->sync(this); - alt = getInterpreter()->adaptivePredict(_input, 116, _ctx); + alt = getInterpreter()->adaptivePredict(_input, 122, _ctx); } } @@ -9063,15 +9164,15 @@ HogQLParser::TableExprContext* HogQLParser::tableExpr(int precedence) { try { size_t alt; enterOuterAlt(_localctx, 1); - setState(1035); + setState(1055); _errHandler->sync(this); - switch (getInterpreter()->adaptivePredict(_input, 117, _ctx)) { + switch (getInterpreter()->adaptivePredict(_input, 123, _ctx)) { case 1: { _localctx = _tracker.createInstance(_localctx); _ctx = _localctx; previousContext = _localctx; - setState(1027); + setState(1047); tableIdentifier(); break; } @@ -9080,7 +9181,7 @@ HogQLParser::TableExprContext* HogQLParser::tableExpr(int precedence) { _localctx = _tracker.createInstance(_localctx); _ctx = _localctx; previousContext = _localctx; - setState(1028); + setState(1048); tableFunctionExpr(); break; } @@ -9089,11 +9190,11 @@ HogQLParser::TableExprContext* HogQLParser::tableExpr(int precedence) { _localctx = _tracker.createInstance(_localctx); _ctx = _localctx; previousContext = _localctx; - setState(1029); + setState(1049); match(HogQLParser::LPAREN); - setState(1030); + setState(1050); selectUnionStmt(); - setState(1031); + setState(1051); match(HogQLParser::RPAREN); break; } @@ -9102,7 +9203,7 @@ HogQLParser::TableExprContext* HogQLParser::tableExpr(int precedence) { _localctx = _tracker.createInstance(_localctx); _ctx = _localctx; previousContext = _localctx; - setState(1033); + setState(1053); hogqlxTagElement(); break; } @@ -9111,7 +9212,7 @@ HogQLParser::TableExprContext* HogQLParser::tableExpr(int precedence) { _localctx = _tracker.createInstance(_localctx); _ctx = _localctx; previousContext = _localctx; - setState(1034); + setState(1054); placeholder(); break; } @@ -9120,9 +9221,9 @@ HogQLParser::TableExprContext* HogQLParser::tableExpr(int precedence) { break; } _ctx->stop = _input->LT(-1); - setState(1045); + setState(1065); _errHandler->sync(this); - alt = getInterpreter()->adaptivePredict(_input, 119, _ctx); + alt = getInterpreter()->adaptivePredict(_input, 125, _ctx); while (alt != 2 && alt != atn::ATN::INVALID_ALT_NUMBER) { if (alt == 1) { if (!_parseListeners.empty()) @@ -9131,10 +9232,10 @@ HogQLParser::TableExprContext* HogQLParser::tableExpr(int precedence) { auto newContext = _tracker.createInstance(_tracker.createInstance(parentContext, parentState)); _localctx = newContext; pushNewRecursionContext(newContext, startState, RuleTableExpr); - setState(1037); + setState(1057); if (!(precpred(_ctx, 3))) throw FailedPredicateException(this, "precpred(_ctx, 3)"); - setState(1041); + setState(1061); _errHandler->sync(this); switch (_input->LA(1)) { case HogQLParser::DATE: @@ -9142,15 +9243,15 @@ HogQLParser::TableExprContext* HogQLParser::tableExpr(int precedence) { case HogQLParser::ID: case HogQLParser::KEY: case HogQLParser::IDENTIFIER: { - setState(1038); + setState(1058); alias(); break; } case HogQLParser::AS: { - setState(1039); + setState(1059); match(HogQLParser::AS); - setState(1040); + setState(1060); identifier(); break; } @@ -9159,9 +9260,9 @@ HogQLParser::TableExprContext* HogQLParser::tableExpr(int precedence) { throw NoViableAltException(this); } } - setState(1047); + setState(1067); _errHandler->sync(this); - alt = getInterpreter()->adaptivePredict(_input, 119, _ctx); + alt = getInterpreter()->adaptivePredict(_input, 125, _ctx); } } catch (RecognitionException &e) { @@ -9221,11 +9322,11 @@ HogQLParser::TableFunctionExprContext* HogQLParser::tableFunctionExpr() { }); try { enterOuterAlt(_localctx, 1); - setState(1048); + setState(1068); identifier(); - setState(1049); + setState(1069); match(HogQLParser::LPAREN); - setState(1051); + setState(1071); _errHandler->sync(this); _la = _input->LA(1); @@ -9233,10 +9334,10 @@ HogQLParser::TableFunctionExprContext* HogQLParser::tableFunctionExpr() { ((1ULL << _la) & -1125900443713538) != 0) || ((((_la - 64) & ~ 0x3fULL) == 0) && ((1ULL << (_la - 64)) & 8076106347046764543) != 0) || ((((_la - 128) & ~ 0x3fULL) == 0) && ((1ULL << (_la - 128)) & 577) != 0)) { - setState(1050); + setState(1070); tableArgList(); } - setState(1053); + setState(1073); match(HogQLParser::RPAREN); } @@ -9293,14 +9394,14 @@ HogQLParser::TableIdentifierContext* HogQLParser::tableIdentifier() { }); try { enterOuterAlt(_localctx, 1); - setState(1058); + setState(1078); _errHandler->sync(this); - switch (getInterpreter()->adaptivePredict(_input, 121, _ctx)) { + switch (getInterpreter()->adaptivePredict(_input, 127, _ctx)) { case 1: { - setState(1055); + setState(1075); databaseIdentifier(); - setState(1056); + setState(1076); match(HogQLParser::DOT); break; } @@ -9308,7 +9409,7 @@ HogQLParser::TableIdentifierContext* HogQLParser::tableIdentifier() { default: break; } - setState(1060); + setState(1080); identifier(); } @@ -9370,17 +9471,17 @@ HogQLParser::TableArgListContext* HogQLParser::tableArgList() { }); try { enterOuterAlt(_localctx, 1); - setState(1062); + setState(1082); columnExpr(0); - setState(1067); + setState(1087); _errHandler->sync(this); _la = _input->LA(1); while (_la == HogQLParser::COMMA) { - setState(1063); + setState(1083); match(HogQLParser::COMMA); - setState(1064); + setState(1084); columnExpr(0); - setState(1069); + setState(1089); _errHandler->sync(this); _la = _input->LA(1); } @@ -9431,7 +9532,7 @@ HogQLParser::DatabaseIdentifierContext* HogQLParser::databaseIdentifier() { }); try { enterOuterAlt(_localctx, 1); - setState(1070); + setState(1090); identifier(); } @@ -9496,21 +9597,21 @@ HogQLParser::FloatingLiteralContext* HogQLParser::floatingLiteral() { exitRule(); }); try { - setState(1080); + setState(1100); _errHandler->sync(this); switch (_input->LA(1)) { case HogQLParser::FLOATING_LITERAL: { enterOuterAlt(_localctx, 1); - setState(1072); + setState(1092); match(HogQLParser::FLOATING_LITERAL); break; } case HogQLParser::DOT: { enterOuterAlt(_localctx, 2); - setState(1073); + setState(1093); match(HogQLParser::DOT); - setState(1074); + setState(1094); _la = _input->LA(1); if (!(_la == HogQLParser::OCTAL_LITERAL @@ -9526,16 +9627,16 @@ HogQLParser::FloatingLiteralContext* HogQLParser::floatingLiteral() { case HogQLParser::DECIMAL_LITERAL: { enterOuterAlt(_localctx, 3); - setState(1075); + setState(1095); match(HogQLParser::DECIMAL_LITERAL); - setState(1076); + setState(1096); match(HogQLParser::DOT); - setState(1078); + setState(1098); _errHandler->sync(this); - switch (getInterpreter()->adaptivePredict(_input, 123, _ctx)) { + switch (getInterpreter()->adaptivePredict(_input, 129, _ctx)) { case 1: { - setState(1077); + setState(1097); _la = _input->LA(1); if (!(_la == HogQLParser::OCTAL_LITERAL @@ -9634,14 +9735,14 @@ HogQLParser::NumberLiteralContext* HogQLParser::numberLiteral() { }); try { enterOuterAlt(_localctx, 1); - setState(1083); + setState(1103); _errHandler->sync(this); _la = _input->LA(1); if (_la == HogQLParser::DASH || _la == HogQLParser::PLUS) { - setState(1082); + setState(1102); _la = _input->LA(1); if (!(_la == HogQLParser::DASH @@ -9653,41 +9754,41 @@ HogQLParser::NumberLiteralContext* HogQLParser::numberLiteral() { consume(); } } - setState(1091); + setState(1111); _errHandler->sync(this); - switch (getInterpreter()->adaptivePredict(_input, 126, _ctx)) { + switch (getInterpreter()->adaptivePredict(_input, 132, _ctx)) { case 1: { - setState(1085); + setState(1105); floatingLiteral(); break; } case 2: { - setState(1086); + setState(1106); match(HogQLParser::OCTAL_LITERAL); break; } case 3: { - setState(1087); + setState(1107); match(HogQLParser::DECIMAL_LITERAL); break; } case 4: { - setState(1088); + setState(1108); match(HogQLParser::HEXADECIMAL_LITERAL); break; } case 5: { - setState(1089); + setState(1109); match(HogQLParser::INF); break; } case 6: { - setState(1090); + setState(1110); match(HogQLParser::NAN_SQL); break; } @@ -9749,7 +9850,7 @@ HogQLParser::LiteralContext* HogQLParser::literal() { exitRule(); }); try { - setState(1096); + setState(1116); _errHandler->sync(this); switch (_input->LA(1)) { case HogQLParser::INF: @@ -9762,21 +9863,21 @@ HogQLParser::LiteralContext* HogQLParser::literal() { case HogQLParser::DOT: case HogQLParser::PLUS: { enterOuterAlt(_localctx, 1); - setState(1093); + setState(1113); numberLiteral(); break; } case HogQLParser::STRING_LITERAL: { enterOuterAlt(_localctx, 2); - setState(1094); + setState(1114); match(HogQLParser::STRING_LITERAL); break; } case HogQLParser::NULL_SQL: { enterOuterAlt(_localctx, 3); - setState(1095); + setState(1115); match(HogQLParser::NULL_SQL); break; } @@ -9860,7 +9961,7 @@ HogQLParser::IntervalContext* HogQLParser::interval() { }); try { enterOuterAlt(_localctx, 1); - setState(1098); + setState(1118); _la = _input->LA(1); if (!((((_la & ~ 0x3fULL) == 0) && ((1ULL << _la) & 27021666484748288) != 0) || ((((_la - 68) & ~ 0x3fULL) == 0) && @@ -10255,7 +10356,7 @@ HogQLParser::KeywordContext* HogQLParser::keyword() { }); try { enterOuterAlt(_localctx, 1); - setState(1100); + setState(1120); _la = _input->LA(1); if (!((((_la & ~ 0x3fULL) == 0) && ((1ULL << _la) & -208293751046537218) != 0) || ((((_la - 64) & ~ 0x3fULL) == 0) && @@ -10326,7 +10427,7 @@ HogQLParser::KeywordForAliasContext* HogQLParser::keywordForAlias() { }); try { enterOuterAlt(_localctx, 1); - setState(1102); + setState(1122); _la = _input->LA(1); if (!((((_la & ~ 0x3fULL) == 0) && ((1ULL << _la) & 70506452090880) != 0))) { @@ -10386,12 +10487,12 @@ HogQLParser::AliasContext* HogQLParser::alias() { exitRule(); }); try { - setState(1106); + setState(1126); _errHandler->sync(this); switch (_input->LA(1)) { case HogQLParser::IDENTIFIER: { enterOuterAlt(_localctx, 1); - setState(1104); + setState(1124); match(HogQLParser::IDENTIFIER); break; } @@ -10401,7 +10502,7 @@ HogQLParser::AliasContext* HogQLParser::alias() { case HogQLParser::ID: case HogQLParser::KEY: { enterOuterAlt(_localctx, 2); - setState(1105); + setState(1125); keywordForAlias(); break; } @@ -10463,12 +10564,12 @@ HogQLParser::IdentifierContext* HogQLParser::identifier() { exitRule(); }); try { - setState(1111); + setState(1131); _errHandler->sync(this); switch (_input->LA(1)) { case HogQLParser::IDENTIFIER: { enterOuterAlt(_localctx, 1); - setState(1108); + setState(1128); match(HogQLParser::IDENTIFIER); break; } @@ -10482,7 +10583,7 @@ HogQLParser::IdentifierContext* HogQLParser::identifier() { case HogQLParser::WEEK: case HogQLParser::YEAR: { enterOuterAlt(_localctx, 2); - setState(1109); + setState(1129); interval(); break; } @@ -10573,7 +10674,7 @@ HogQLParser::IdentifierContext* HogQLParser::identifier() { case HogQLParser::WINDOW: case HogQLParser::WITH: { enterOuterAlt(_localctx, 3); - setState(1110); + setState(1130); keyword(); break; } @@ -10636,11 +10737,11 @@ HogQLParser::EnumValueContext* HogQLParser::enumValue() { }); try { enterOuterAlt(_localctx, 1); - setState(1113); + setState(1133); string(); - setState(1114); + setState(1134); match(HogQLParser::EQ_SINGLE); - setState(1115); + setState(1135); numberLiteral(); } @@ -10697,11 +10798,11 @@ HogQLParser::PlaceholderContext* HogQLParser::placeholder() { }); try { enterOuterAlt(_localctx, 1); - setState(1117); + setState(1137); match(HogQLParser::LBRACE); - setState(1118); + setState(1138); identifier(); - setState(1119); + setState(1139); match(HogQLParser::RBRACE); } @@ -10753,19 +10854,19 @@ HogQLParser::StringContext* HogQLParser::string() { exitRule(); }); try { - setState(1123); + setState(1143); _errHandler->sync(this); switch (_input->LA(1)) { case HogQLParser::STRING_LITERAL: { enterOuterAlt(_localctx, 1); - setState(1121); + setState(1141); match(HogQLParser::STRING_LITERAL); break; } case HogQLParser::QUOTE_SINGLE_TEMPLATE: { enterOuterAlt(_localctx, 2); - setState(1122); + setState(1142); templateString(); break; } @@ -10833,21 +10934,21 @@ HogQLParser::TemplateStringContext* HogQLParser::templateString() { }); try { enterOuterAlt(_localctx, 1); - setState(1125); + setState(1145); match(HogQLParser::QUOTE_SINGLE_TEMPLATE); - setState(1129); + setState(1149); _errHandler->sync(this); _la = _input->LA(1); while (_la == HogQLParser::STRING_TEXT || _la == HogQLParser::STRING_ESCAPE_TRIGGER) { - setState(1126); + setState(1146); stringContents(); - setState(1131); + setState(1151); _errHandler->sync(this); _la = _input->LA(1); } - setState(1132); + setState(1152); match(HogQLParser::QUOTE_SINGLE); } @@ -10907,23 +11008,23 @@ HogQLParser::StringContentsContext* HogQLParser::stringContents() { exitRule(); }); try { - setState(1139); + setState(1159); _errHandler->sync(this); switch (_input->LA(1)) { case HogQLParser::STRING_ESCAPE_TRIGGER: { enterOuterAlt(_localctx, 1); - setState(1134); + setState(1154); match(HogQLParser::STRING_ESCAPE_TRIGGER); - setState(1135); + setState(1155); columnExpr(0); - setState(1136); + setState(1156); match(HogQLParser::RBRACE); break; } case HogQLParser::STRING_TEXT: { enterOuterAlt(_localctx, 2); - setState(1138); + setState(1158); match(HogQLParser::STRING_TEXT); break; } @@ -10991,21 +11092,21 @@ HogQLParser::FullTemplateStringContext* HogQLParser::fullTemplateString() { }); try { enterOuterAlt(_localctx, 1); - setState(1141); + setState(1161); match(HogQLParser::QUOTE_SINGLE_TEMPLATE_FULL); - setState(1145); + setState(1165); _errHandler->sync(this); _la = _input->LA(1); while (_la == HogQLParser::FULL_STRING_TEXT || _la == HogQLParser::FULL_STRING_ESCAPE_TRIGGER) { - setState(1142); + setState(1162); stringContentsFull(); - setState(1147); + setState(1167); _errHandler->sync(this); _la = _input->LA(1); } - setState(1148); + setState(1168); match(HogQLParser::EOF); } @@ -11065,23 +11166,23 @@ HogQLParser::StringContentsFullContext* HogQLParser::stringContentsFull() { exitRule(); }); try { - setState(1155); + setState(1175); _errHandler->sync(this); switch (_input->LA(1)) { case HogQLParser::FULL_STRING_ESCAPE_TRIGGER: { enterOuterAlt(_localctx, 1); - setState(1150); + setState(1170); match(HogQLParser::FULL_STRING_ESCAPE_TRIGGER); - setState(1151); + setState(1171); columnExpr(0); - setState(1152); + setState(1172); match(HogQLParser::RBRACE); break; } case HogQLParser::FULL_STRING_TEXT: { enterOuterAlt(_localctx, 2); - setState(1154); + setState(1174); match(HogQLParser::FULL_STRING_TEXT); break; } diff --git a/hogql_parser/HogQLParser.h b/hogql_parser/HogQLParser.h index 174d2572e5736..94f46d07b4562 100644 --- a/hogql_parser/HogQLParser.h +++ b/hogql_parser/HogQLParser.h @@ -1439,9 +1439,13 @@ class HogQLParser : public antlr4::Parser { std::vector identifier(); IdentifierContext* identifier(size_t i); antlr4::tree::TerminalNode *OVER(); - antlr4::tree::TerminalNode *LPAREN(); - antlr4::tree::TerminalNode *RPAREN(); + std::vector LPAREN(); + antlr4::tree::TerminalNode* LPAREN(size_t i); + std::vector RPAREN(); + antlr4::tree::TerminalNode* RPAREN(size_t i); ColumnExprListContext *columnExprList(); + antlr4::tree::TerminalNode *DISTINCT(); + ColumnArgListContext *columnArgList(); virtual std::any accept(antlr4::tree::ParseTreeVisitor *visitor) override; }; @@ -1635,6 +1639,8 @@ class HogQLParser : public antlr4::Parser { std::vector RPAREN(); antlr4::tree::TerminalNode* RPAREN(size_t i); ColumnExprListContext *columnExprList(); + antlr4::tree::TerminalNode *DISTINCT(); + ColumnArgListContext *columnArgList(); virtual std::any accept(antlr4::tree::ParseTreeVisitor *visitor) override; }; diff --git a/hogql_parser/HogQLParser.interp b/hogql_parser/HogQLParser.interp index a2f030a7eb8fa..086eca220c32f 100644 --- a/hogql_parser/HogQLParser.interp +++ b/hogql_parser/HogQLParser.interp @@ -399,4 +399,4 @@ stringContentsFull atn: -[4, 1, 154, 1158, 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, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, 41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, 2, 47, 7, 47, 2, 48, 7, 48, 2, 49, 7, 49, 2, 50, 7, 50, 2, 51, 7, 51, 2, 52, 7, 52, 2, 53, 7, 53, 2, 54, 7, 54, 2, 55, 7, 55, 2, 56, 7, 56, 2, 57, 7, 57, 2, 58, 7, 58, 2, 59, 7, 59, 2, 60, 7, 60, 2, 61, 7, 61, 2, 62, 7, 62, 2, 63, 7, 63, 2, 64, 7, 64, 2, 65, 7, 65, 2, 66, 7, 66, 2, 67, 7, 67, 2, 68, 7, 68, 2, 69, 7, 69, 2, 70, 7, 70, 2, 71, 7, 71, 2, 72, 7, 72, 2, 73, 7, 73, 2, 74, 7, 74, 2, 75, 7, 75, 2, 76, 7, 76, 2, 77, 7, 77, 2, 78, 7, 78, 2, 79, 7, 79, 2, 80, 7, 80, 2, 81, 7, 81, 2, 82, 7, 82, 1, 0, 5, 0, 168, 8, 0, 10, 0, 12, 0, 171, 9, 0, 1, 0, 1, 0, 1, 1, 1, 1, 3, 1, 177, 8, 1, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 3, 3, 186, 8, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 5, 5, 199, 8, 5, 10, 5, 12, 5, 202, 9, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 3, 6, 213, 8, 6, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 3, 8, 225, 8, 8, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, 1, 11, 3, 11, 241, 8, 11, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 13, 1, 13, 5, 13, 250, 8, 13, 10, 13, 12, 13, 253, 9, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 1, 14, 1, 15, 1, 15, 1, 15, 5, 15, 264, 8, 15, 10, 15, 12, 15, 267, 9, 15, 1, 16, 1, 16, 1, 16, 3, 16, 272, 8, 16, 1, 16, 1, 16, 1, 17, 1, 17, 1, 17, 1, 17, 5, 17, 280, 8, 17, 10, 17, 12, 17, 283, 9, 17, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 3, 18, 291, 8, 18, 1, 19, 3, 19, 294, 8, 19, 1, 19, 1, 19, 3, 19, 298, 8, 19, 1, 19, 3, 19, 301, 8, 19, 1, 19, 1, 19, 3, 19, 305, 8, 19, 1, 19, 3, 19, 308, 8, 19, 1, 19, 3, 19, 311, 8, 19, 1, 19, 3, 19, 314, 8, 19, 1, 19, 3, 19, 317, 8, 19, 1, 19, 1, 19, 3, 19, 321, 8, 19, 1, 19, 1, 19, 3, 19, 325, 8, 19, 1, 19, 3, 19, 328, 8, 19, 1, 19, 3, 19, 331, 8, 19, 1, 19, 3, 19, 334, 8, 19, 1, 19, 1, 19, 3, 19, 338, 8, 19, 1, 19, 3, 19, 341, 8, 19, 1, 20, 1, 20, 1, 20, 1, 21, 1, 21, 1, 21, 1, 21, 3, 21, 350, 8, 21, 1, 22, 1, 22, 1, 22, 1, 23, 3, 23, 356, 8, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 5, 24, 375, 8, 24, 10, 24, 12, 24, 378, 9, 24, 1, 25, 1, 25, 1, 25, 1, 26, 1, 26, 1, 26, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 3, 27, 394, 8, 27, 1, 28, 1, 28, 1, 28, 1, 29, 1, 29, 1, 29, 1, 29, 1, 30, 1, 30, 1, 30, 1, 30, 1, 31, 1, 31, 1, 31, 1, 31, 3, 31, 411, 8, 31, 1, 31, 1, 31, 1, 31, 1, 31, 3, 31, 417, 8, 31, 1, 31, 1, 31, 1, 31, 1, 31, 3, 31, 423, 8, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 3, 31, 434, 8, 31, 3, 31, 436, 8, 31, 1, 32, 1, 32, 1, 32, 1, 33, 1, 33, 1, 33, 1, 34, 1, 34, 1, 34, 3, 34, 447, 8, 34, 1, 34, 3, 34, 450, 8, 34, 1, 34, 1, 34, 1, 34, 1, 34, 3, 34, 456, 8, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 3, 34, 464, 8, 34, 1, 34, 1, 34, 1, 34, 1, 34, 5, 34, 470, 8, 34, 10, 34, 12, 34, 473, 9, 34, 1, 35, 3, 35, 476, 8, 35, 1, 35, 1, 35, 1, 35, 3, 35, 481, 8, 35, 1, 35, 3, 35, 484, 8, 35, 1, 35, 3, 35, 487, 8, 35, 1, 35, 1, 35, 3, 35, 491, 8, 35, 1, 35, 1, 35, 3, 35, 495, 8, 35, 1, 35, 3, 35, 498, 8, 35, 3, 35, 500, 8, 35, 1, 35, 3, 35, 503, 8, 35, 1, 35, 1, 35, 3, 35, 507, 8, 35, 1, 35, 1, 35, 3, 35, 511, 8, 35, 1, 35, 3, 35, 514, 8, 35, 3, 35, 516, 8, 35, 3, 35, 518, 8, 35, 1, 36, 1, 36, 1, 36, 3, 36, 523, 8, 36, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 3, 37, 534, 8, 37, 1, 38, 1, 38, 1, 38, 1, 38, 3, 38, 540, 8, 38, 1, 39, 1, 39, 1, 39, 5, 39, 545, 8, 39, 10, 39, 12, 39, 548, 9, 39, 1, 40, 1, 40, 3, 40, 552, 8, 40, 1, 40, 1, 40, 3, 40, 556, 8, 40, 1, 40, 1, 40, 3, 40, 560, 8, 40, 1, 41, 1, 41, 1, 41, 1, 41, 3, 41, 566, 8, 41, 3, 41, 568, 8, 41, 1, 42, 1, 42, 1, 42, 5, 42, 573, 8, 42, 10, 42, 12, 42, 576, 9, 42, 1, 43, 1, 43, 1, 43, 1, 43, 1, 44, 3, 44, 583, 8, 44, 1, 44, 3, 44, 586, 8, 44, 1, 44, 3, 44, 589, 8, 44, 1, 45, 1, 45, 1, 45, 1, 45, 1, 46, 1, 46, 1, 46, 1, 46, 1, 47, 1, 47, 1, 47, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 3, 48, 608, 8, 48, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 3, 49, 622, 8, 49, 1, 50, 1, 50, 1, 50, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 5, 51, 636, 8, 51, 10, 51, 12, 51, 639, 9, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 5, 51, 648, 8, 51, 10, 51, 12, 51, 651, 9, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 5, 51, 660, 8, 51, 10, 51, 12, 51, 663, 9, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 3, 51, 670, 8, 51, 1, 51, 1, 51, 3, 51, 674, 8, 51, 1, 52, 1, 52, 1, 52, 5, 52, 679, 8, 52, 10, 52, 12, 52, 682, 9, 52, 1, 53, 1, 53, 1, 53, 3, 53, 687, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 4, 53, 694, 8, 53, 11, 53, 12, 53, 695, 1, 53, 1, 53, 3, 53, 700, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 724, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 741, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 753, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 763, 8, 53, 1, 53, 3, 53, 766, 8, 53, 1, 53, 1, 53, 3, 53, 770, 8, 53, 1, 53, 3, 53, 773, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 787, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 804, 8, 53, 1, 53, 1, 53, 1, 53, 3, 53, 809, 8, 53, 1, 53, 1, 53, 3, 53, 813, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 819, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 826, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 838, 8, 53, 1, 53, 1, 53, 3, 53, 842, 8, 53, 1, 53, 3, 53, 845, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 854, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 868, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 895, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 904, 8, 53, 5, 53, 906, 8, 53, 10, 53, 12, 53, 909, 9, 53, 1, 54, 1, 54, 1, 54, 5, 54, 914, 8, 54, 10, 54, 12, 54, 917, 9, 54, 1, 55, 1, 55, 3, 55, 921, 8, 55, 1, 56, 1, 56, 1, 56, 1, 56, 5, 56, 927, 8, 56, 10, 56, 12, 56, 930, 9, 56, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 5, 56, 937, 8, 56, 10, 56, 12, 56, 940, 9, 56, 3, 56, 942, 8, 56, 1, 56, 1, 56, 1, 56, 1, 57, 1, 57, 1, 57, 5, 57, 950, 8, 57, 10, 57, 12, 57, 953, 9, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 5, 57, 961, 8, 57, 10, 57, 12, 57, 964, 9, 57, 1, 57, 1, 57, 3, 57, 968, 8, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 3, 57, 975, 8, 57, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 3, 58, 988, 8, 58, 1, 59, 1, 59, 1, 59, 5, 59, 993, 8, 59, 10, 59, 12, 59, 996, 9, 59, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 3, 60, 1008, 8, 60, 1, 61, 1, 61, 1, 61, 1, 61, 3, 61, 1014, 8, 61, 1, 61, 3, 61, 1017, 8, 61, 1, 62, 1, 62, 1, 62, 5, 62, 1022, 8, 62, 10, 62, 12, 62, 1025, 9, 62, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 3, 63, 1036, 8, 63, 1, 63, 1, 63, 1, 63, 1, 63, 3, 63, 1042, 8, 63, 5, 63, 1044, 8, 63, 10, 63, 12, 63, 1047, 9, 63, 1, 64, 1, 64, 1, 64, 3, 64, 1052, 8, 64, 1, 64, 1, 64, 1, 65, 1, 65, 1, 65, 3, 65, 1059, 8, 65, 1, 65, 1, 65, 1, 66, 1, 66, 1, 66, 5, 66, 1066, 8, 66, 10, 66, 12, 66, 1069, 9, 66, 1, 67, 1, 67, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 3, 68, 1079, 8, 68, 3, 68, 1081, 8, 68, 1, 69, 3, 69, 1084, 8, 69, 1, 69, 1, 69, 1, 69, 1, 69, 1, 69, 1, 69, 3, 69, 1092, 8, 69, 1, 70, 1, 70, 1, 70, 3, 70, 1097, 8, 70, 1, 71, 1, 71, 1, 72, 1, 72, 1, 73, 1, 73, 1, 74, 1, 74, 3, 74, 1107, 8, 74, 1, 75, 1, 75, 1, 75, 3, 75, 1112, 8, 75, 1, 76, 1, 76, 1, 76, 1, 76, 1, 77, 1, 77, 1, 77, 1, 77, 1, 78, 1, 78, 3, 78, 1124, 8, 78, 1, 79, 1, 79, 5, 79, 1128, 8, 79, 10, 79, 12, 79, 1131, 9, 79, 1, 79, 1, 79, 1, 80, 1, 80, 1, 80, 1, 80, 1, 80, 3, 80, 1140, 8, 80, 1, 81, 1, 81, 5, 81, 1144, 8, 81, 10, 81, 12, 81, 1147, 9, 81, 1, 81, 1, 81, 1, 82, 1, 82, 1, 82, 1, 82, 1, 82, 3, 82, 1156, 8, 82, 1, 82, 0, 3, 68, 106, 126, 83, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 0, 16, 2, 0, 17, 17, 72, 72, 2, 0, 42, 42, 49, 49, 3, 0, 1, 1, 4, 4, 8, 8, 4, 0, 1, 1, 3, 4, 8, 8, 78, 78, 2, 0, 49, 49, 71, 71, 2, 0, 1, 1, 4, 4, 2, 0, 7, 7, 21, 22, 2, 0, 28, 28, 47, 47, 2, 0, 69, 69, 74, 74, 3, 0, 10, 10, 48, 48, 87, 87, 2, 0, 39, 39, 51, 51, 1, 0, 103, 104, 2, 0, 114, 114, 134, 134, 7, 0, 20, 20, 36, 36, 53, 54, 68, 68, 76, 76, 93, 93, 99, 99, 12, 0, 1, 19, 21, 28, 30, 35, 37, 40, 42, 49, 51, 52, 56, 56, 58, 67, 69, 75, 77, 92, 94, 95, 97, 98, 4, 0, 19, 19, 28, 28, 37, 37, 46, 46, 1288, 0, 169, 1, 0, 0, 0, 2, 176, 1, 0, 0, 0, 4, 178, 1, 0, 0, 0, 6, 180, 1, 0, 0, 0, 8, 189, 1, 0, 0, 0, 10, 195, 1, 0, 0, 0, 12, 212, 1, 0, 0, 0, 14, 214, 1, 0, 0, 0, 16, 217, 1, 0, 0, 0, 18, 226, 1, 0, 0, 0, 20, 232, 1, 0, 0, 0, 22, 236, 1, 0, 0, 0, 24, 245, 1, 0, 0, 0, 26, 247, 1, 0, 0, 0, 28, 256, 1, 0, 0, 0, 30, 260, 1, 0, 0, 0, 32, 271, 1, 0, 0, 0, 34, 275, 1, 0, 0, 0, 36, 290, 1, 0, 0, 0, 38, 293, 1, 0, 0, 0, 40, 342, 1, 0, 0, 0, 42, 345, 1, 0, 0, 0, 44, 351, 1, 0, 0, 0, 46, 355, 1, 0, 0, 0, 48, 361, 1, 0, 0, 0, 50, 379, 1, 0, 0, 0, 52, 382, 1, 0, 0, 0, 54, 385, 1, 0, 0, 0, 56, 395, 1, 0, 0, 0, 58, 398, 1, 0, 0, 0, 60, 402, 1, 0, 0, 0, 62, 435, 1, 0, 0, 0, 64, 437, 1, 0, 0, 0, 66, 440, 1, 0, 0, 0, 68, 455, 1, 0, 0, 0, 70, 517, 1, 0, 0, 0, 72, 522, 1, 0, 0, 0, 74, 533, 1, 0, 0, 0, 76, 535, 1, 0, 0, 0, 78, 541, 1, 0, 0, 0, 80, 549, 1, 0, 0, 0, 82, 567, 1, 0, 0, 0, 84, 569, 1, 0, 0, 0, 86, 577, 1, 0, 0, 0, 88, 582, 1, 0, 0, 0, 90, 590, 1, 0, 0, 0, 92, 594, 1, 0, 0, 0, 94, 598, 1, 0, 0, 0, 96, 607, 1, 0, 0, 0, 98, 621, 1, 0, 0, 0, 100, 623, 1, 0, 0, 0, 102, 673, 1, 0, 0, 0, 104, 675, 1, 0, 0, 0, 106, 812, 1, 0, 0, 0, 108, 910, 1, 0, 0, 0, 110, 920, 1, 0, 0, 0, 112, 941, 1, 0, 0, 0, 114, 974, 1, 0, 0, 0, 116, 987, 1, 0, 0, 0, 118, 989, 1, 0, 0, 0, 120, 1007, 1, 0, 0, 0, 122, 1016, 1, 0, 0, 0, 124, 1018, 1, 0, 0, 0, 126, 1035, 1, 0, 0, 0, 128, 1048, 1, 0, 0, 0, 130, 1058, 1, 0, 0, 0, 132, 1062, 1, 0, 0, 0, 134, 1070, 1, 0, 0, 0, 136, 1080, 1, 0, 0, 0, 138, 1083, 1, 0, 0, 0, 140, 1096, 1, 0, 0, 0, 142, 1098, 1, 0, 0, 0, 144, 1100, 1, 0, 0, 0, 146, 1102, 1, 0, 0, 0, 148, 1106, 1, 0, 0, 0, 150, 1111, 1, 0, 0, 0, 152, 1113, 1, 0, 0, 0, 154, 1117, 1, 0, 0, 0, 156, 1123, 1, 0, 0, 0, 158, 1125, 1, 0, 0, 0, 160, 1139, 1, 0, 0, 0, 162, 1141, 1, 0, 0, 0, 164, 1155, 1, 0, 0, 0, 166, 168, 3, 2, 1, 0, 167, 166, 1, 0, 0, 0, 168, 171, 1, 0, 0, 0, 169, 167, 1, 0, 0, 0, 169, 170, 1, 0, 0, 0, 170, 172, 1, 0, 0, 0, 171, 169, 1, 0, 0, 0, 172, 173, 5, 0, 0, 1, 173, 1, 1, 0, 0, 0, 174, 177, 3, 6, 3, 0, 175, 177, 3, 12, 6, 0, 176, 174, 1, 0, 0, 0, 176, 175, 1, 0, 0, 0, 177, 3, 1, 0, 0, 0, 178, 179, 3, 106, 53, 0, 179, 5, 1, 0, 0, 0, 180, 181, 5, 50, 0, 0, 181, 185, 3, 150, 75, 0, 182, 183, 5, 111, 0, 0, 183, 184, 5, 118, 0, 0, 184, 186, 3, 4, 2, 0, 185, 182, 1, 0, 0, 0, 185, 186, 1, 0, 0, 0, 186, 187, 1, 0, 0, 0, 187, 188, 5, 145, 0, 0, 188, 7, 1, 0, 0, 0, 189, 190, 3, 4, 2, 0, 190, 191, 5, 111, 0, 0, 191, 192, 5, 118, 0, 0, 192, 193, 3, 4, 2, 0, 193, 194, 5, 145, 0, 0, 194, 9, 1, 0, 0, 0, 195, 200, 3, 150, 75, 0, 196, 197, 5, 112, 0, 0, 197, 199, 3, 150, 75, 0, 198, 196, 1, 0, 0, 0, 199, 202, 1, 0, 0, 0, 200, 198, 1, 0, 0, 0, 200, 201, 1, 0, 0, 0, 201, 11, 1, 0, 0, 0, 202, 200, 1, 0, 0, 0, 203, 213, 3, 20, 10, 0, 204, 213, 3, 24, 12, 0, 205, 213, 3, 14, 7, 0, 206, 213, 3, 16, 8, 0, 207, 213, 3, 18, 9, 0, 208, 213, 3, 22, 11, 0, 209, 213, 3, 8, 4, 0, 210, 213, 3, 20, 10, 0, 211, 213, 3, 26, 13, 0, 212, 203, 1, 0, 0, 0, 212, 204, 1, 0, 0, 0, 212, 205, 1, 0, 0, 0, 212, 206, 1, 0, 0, 0, 212, 207, 1, 0, 0, 0, 212, 208, 1, 0, 0, 0, 212, 209, 1, 0, 0, 0, 212, 210, 1, 0, 0, 0, 212, 211, 1, 0, 0, 0, 213, 13, 1, 0, 0, 0, 214, 215, 3, 4, 2, 0, 215, 216, 5, 145, 0, 0, 216, 15, 1, 0, 0, 0, 217, 218, 5, 38, 0, 0, 218, 219, 5, 126, 0, 0, 219, 220, 3, 4, 2, 0, 220, 221, 5, 144, 0, 0, 221, 224, 3, 12, 6, 0, 222, 223, 5, 24, 0, 0, 223, 225, 3, 12, 6, 0, 224, 222, 1, 0, 0, 0, 224, 225, 1, 0, 0, 0, 225, 17, 1, 0, 0, 0, 226, 227, 5, 96, 0, 0, 227, 228, 5, 126, 0, 0, 228, 229, 3, 4, 2, 0, 229, 230, 5, 144, 0, 0, 230, 231, 3, 12, 6, 0, 231, 19, 1, 0, 0, 0, 232, 233, 5, 70, 0, 0, 233, 234, 3, 4, 2, 0, 234, 235, 5, 145, 0, 0, 235, 21, 1, 0, 0, 0, 236, 237, 5, 29, 0, 0, 237, 238, 3, 150, 75, 0, 238, 240, 5, 126, 0, 0, 239, 241, 3, 10, 5, 0, 240, 239, 1, 0, 0, 0, 240, 241, 1, 0, 0, 0, 241, 242, 1, 0, 0, 0, 242, 243, 5, 144, 0, 0, 243, 244, 3, 26, 13, 0, 244, 23, 1, 0, 0, 0, 245, 246, 5, 145, 0, 0, 246, 25, 1, 0, 0, 0, 247, 251, 5, 124, 0, 0, 248, 250, 3, 2, 1, 0, 249, 248, 1, 0, 0, 0, 250, 253, 1, 0, 0, 0, 251, 249, 1, 0, 0, 0, 251, 252, 1, 0, 0, 0, 252, 254, 1, 0, 0, 0, 253, 251, 1, 0, 0, 0, 254, 255, 5, 142, 0, 0, 255, 27, 1, 0, 0, 0, 256, 257, 3, 4, 2, 0, 257, 258, 5, 111, 0, 0, 258, 259, 3, 4, 2, 0, 259, 29, 1, 0, 0, 0, 260, 265, 3, 28, 14, 0, 261, 262, 5, 112, 0, 0, 262, 264, 3, 28, 14, 0, 263, 261, 1, 0, 0, 0, 264, 267, 1, 0, 0, 0, 265, 263, 1, 0, 0, 0, 265, 266, 1, 0, 0, 0, 266, 31, 1, 0, 0, 0, 267, 265, 1, 0, 0, 0, 268, 272, 3, 34, 17, 0, 269, 272, 3, 38, 19, 0, 270, 272, 3, 114, 57, 0, 271, 268, 1, 0, 0, 0, 271, 269, 1, 0, 0, 0, 271, 270, 1, 0, 0, 0, 272, 273, 1, 0, 0, 0, 273, 274, 5, 0, 0, 1, 274, 33, 1, 0, 0, 0, 275, 281, 3, 36, 18, 0, 276, 277, 5, 91, 0, 0, 277, 278, 5, 1, 0, 0, 278, 280, 3, 36, 18, 0, 279, 276, 1, 0, 0, 0, 280, 283, 1, 0, 0, 0, 281, 279, 1, 0, 0, 0, 281, 282, 1, 0, 0, 0, 282, 35, 1, 0, 0, 0, 283, 281, 1, 0, 0, 0, 284, 291, 3, 38, 19, 0, 285, 286, 5, 126, 0, 0, 286, 287, 3, 34, 17, 0, 287, 288, 5, 144, 0, 0, 288, 291, 1, 0, 0, 0, 289, 291, 3, 154, 77, 0, 290, 284, 1, 0, 0, 0, 290, 285, 1, 0, 0, 0, 290, 289, 1, 0, 0, 0, 291, 37, 1, 0, 0, 0, 292, 294, 3, 40, 20, 0, 293, 292, 1, 0, 0, 0, 293, 294, 1, 0, 0, 0, 294, 295, 1, 0, 0, 0, 295, 297, 5, 77, 0, 0, 296, 298, 5, 23, 0, 0, 297, 296, 1, 0, 0, 0, 297, 298, 1, 0, 0, 0, 298, 300, 1, 0, 0, 0, 299, 301, 3, 42, 21, 0, 300, 299, 1, 0, 0, 0, 300, 301, 1, 0, 0, 0, 301, 302, 1, 0, 0, 0, 302, 304, 3, 104, 52, 0, 303, 305, 3, 44, 22, 0, 304, 303, 1, 0, 0, 0, 304, 305, 1, 0, 0, 0, 305, 307, 1, 0, 0, 0, 306, 308, 3, 46, 23, 0, 307, 306, 1, 0, 0, 0, 307, 308, 1, 0, 0, 0, 308, 310, 1, 0, 0, 0, 309, 311, 3, 50, 25, 0, 310, 309, 1, 0, 0, 0, 310, 311, 1, 0, 0, 0, 311, 313, 1, 0, 0, 0, 312, 314, 3, 52, 26, 0, 313, 312, 1, 0, 0, 0, 313, 314, 1, 0, 0, 0, 314, 316, 1, 0, 0, 0, 315, 317, 3, 54, 27, 0, 316, 315, 1, 0, 0, 0, 316, 317, 1, 0, 0, 0, 317, 320, 1, 0, 0, 0, 318, 319, 5, 98, 0, 0, 319, 321, 7, 0, 0, 0, 320, 318, 1, 0, 0, 0, 320, 321, 1, 0, 0, 0, 321, 324, 1, 0, 0, 0, 322, 323, 5, 98, 0, 0, 323, 325, 5, 86, 0, 0, 324, 322, 1, 0, 0, 0, 324, 325, 1, 0, 0, 0, 325, 327, 1, 0, 0, 0, 326, 328, 3, 56, 28, 0, 327, 326, 1, 0, 0, 0, 327, 328, 1, 0, 0, 0, 328, 330, 1, 0, 0, 0, 329, 331, 3, 48, 24, 0, 330, 329, 1, 0, 0, 0, 330, 331, 1, 0, 0, 0, 331, 333, 1, 0, 0, 0, 332, 334, 3, 58, 29, 0, 333, 332, 1, 0, 0, 0, 333, 334, 1, 0, 0, 0, 334, 337, 1, 0, 0, 0, 335, 338, 3, 62, 31, 0, 336, 338, 3, 64, 32, 0, 337, 335, 1, 0, 0, 0, 337, 336, 1, 0, 0, 0, 337, 338, 1, 0, 0, 0, 338, 340, 1, 0, 0, 0, 339, 341, 3, 66, 33, 0, 340, 339, 1, 0, 0, 0, 340, 341, 1, 0, 0, 0, 341, 39, 1, 0, 0, 0, 342, 343, 5, 98, 0, 0, 343, 344, 3, 118, 59, 0, 344, 41, 1, 0, 0, 0, 345, 346, 5, 85, 0, 0, 346, 349, 5, 104, 0, 0, 347, 348, 5, 98, 0, 0, 348, 350, 5, 82, 0, 0, 349, 347, 1, 0, 0, 0, 349, 350, 1, 0, 0, 0, 350, 43, 1, 0, 0, 0, 351, 352, 5, 32, 0, 0, 352, 353, 3, 68, 34, 0, 353, 45, 1, 0, 0, 0, 354, 356, 7, 1, 0, 0, 355, 354, 1, 0, 0, 0, 355, 356, 1, 0, 0, 0, 356, 357, 1, 0, 0, 0, 357, 358, 5, 5, 0, 0, 358, 359, 5, 45, 0, 0, 359, 360, 3, 104, 52, 0, 360, 47, 1, 0, 0, 0, 361, 362, 5, 97, 0, 0, 362, 363, 3, 150, 75, 0, 363, 364, 5, 6, 0, 0, 364, 365, 5, 126, 0, 0, 365, 366, 3, 88, 44, 0, 366, 376, 5, 144, 0, 0, 367, 368, 5, 112, 0, 0, 368, 369, 3, 150, 75, 0, 369, 370, 5, 6, 0, 0, 370, 371, 5, 126, 0, 0, 371, 372, 3, 88, 44, 0, 372, 373, 5, 144, 0, 0, 373, 375, 1, 0, 0, 0, 374, 367, 1, 0, 0, 0, 375, 378, 1, 0, 0, 0, 376, 374, 1, 0, 0, 0, 376, 377, 1, 0, 0, 0, 377, 49, 1, 0, 0, 0, 378, 376, 1, 0, 0, 0, 379, 380, 5, 67, 0, 0, 380, 381, 3, 106, 53, 0, 381, 51, 1, 0, 0, 0, 382, 383, 5, 95, 0, 0, 383, 384, 3, 106, 53, 0, 384, 53, 1, 0, 0, 0, 385, 386, 5, 34, 0, 0, 386, 393, 5, 11, 0, 0, 387, 388, 7, 0, 0, 0, 388, 389, 5, 126, 0, 0, 389, 390, 3, 104, 52, 0, 390, 391, 5, 144, 0, 0, 391, 394, 1, 0, 0, 0, 392, 394, 3, 104, 52, 0, 393, 387, 1, 0, 0, 0, 393, 392, 1, 0, 0, 0, 394, 55, 1, 0, 0, 0, 395, 396, 5, 35, 0, 0, 396, 397, 3, 106, 53, 0, 397, 57, 1, 0, 0, 0, 398, 399, 5, 62, 0, 0, 399, 400, 5, 11, 0, 0, 400, 401, 3, 78, 39, 0, 401, 59, 1, 0, 0, 0, 402, 403, 5, 62, 0, 0, 403, 404, 5, 11, 0, 0, 404, 405, 3, 104, 52, 0, 405, 61, 1, 0, 0, 0, 406, 407, 5, 52, 0, 0, 407, 410, 3, 106, 53, 0, 408, 409, 5, 112, 0, 0, 409, 411, 3, 106, 53, 0, 410, 408, 1, 0, 0, 0, 410, 411, 1, 0, 0, 0, 411, 416, 1, 0, 0, 0, 412, 413, 5, 98, 0, 0, 413, 417, 5, 82, 0, 0, 414, 415, 5, 11, 0, 0, 415, 417, 3, 104, 52, 0, 416, 412, 1, 0, 0, 0, 416, 414, 1, 0, 0, 0, 416, 417, 1, 0, 0, 0, 417, 436, 1, 0, 0, 0, 418, 419, 5, 52, 0, 0, 419, 422, 3, 106, 53, 0, 420, 421, 5, 98, 0, 0, 421, 423, 5, 82, 0, 0, 422, 420, 1, 0, 0, 0, 422, 423, 1, 0, 0, 0, 423, 424, 1, 0, 0, 0, 424, 425, 5, 59, 0, 0, 425, 426, 3, 106, 53, 0, 426, 436, 1, 0, 0, 0, 427, 428, 5, 52, 0, 0, 428, 429, 3, 106, 53, 0, 429, 430, 5, 59, 0, 0, 430, 433, 3, 106, 53, 0, 431, 432, 5, 11, 0, 0, 432, 434, 3, 104, 52, 0, 433, 431, 1, 0, 0, 0, 433, 434, 1, 0, 0, 0, 434, 436, 1, 0, 0, 0, 435, 406, 1, 0, 0, 0, 435, 418, 1, 0, 0, 0, 435, 427, 1, 0, 0, 0, 436, 63, 1, 0, 0, 0, 437, 438, 5, 59, 0, 0, 438, 439, 3, 106, 53, 0, 439, 65, 1, 0, 0, 0, 440, 441, 5, 79, 0, 0, 441, 442, 3, 84, 42, 0, 442, 67, 1, 0, 0, 0, 443, 444, 6, 34, -1, 0, 444, 446, 3, 126, 63, 0, 445, 447, 5, 27, 0, 0, 446, 445, 1, 0, 0, 0, 446, 447, 1, 0, 0, 0, 447, 449, 1, 0, 0, 0, 448, 450, 3, 76, 38, 0, 449, 448, 1, 0, 0, 0, 449, 450, 1, 0, 0, 0, 450, 456, 1, 0, 0, 0, 451, 452, 5, 126, 0, 0, 452, 453, 3, 68, 34, 0, 453, 454, 5, 144, 0, 0, 454, 456, 1, 0, 0, 0, 455, 443, 1, 0, 0, 0, 455, 451, 1, 0, 0, 0, 456, 471, 1, 0, 0, 0, 457, 458, 10, 3, 0, 0, 458, 459, 3, 72, 36, 0, 459, 460, 3, 68, 34, 4, 460, 470, 1, 0, 0, 0, 461, 463, 10, 4, 0, 0, 462, 464, 3, 70, 35, 0, 463, 462, 1, 0, 0, 0, 463, 464, 1, 0, 0, 0, 464, 465, 1, 0, 0, 0, 465, 466, 5, 45, 0, 0, 466, 467, 3, 68, 34, 0, 467, 468, 3, 74, 37, 0, 468, 470, 1, 0, 0, 0, 469, 457, 1, 0, 0, 0, 469, 461, 1, 0, 0, 0, 470, 473, 1, 0, 0, 0, 471, 469, 1, 0, 0, 0, 471, 472, 1, 0, 0, 0, 472, 69, 1, 0, 0, 0, 473, 471, 1, 0, 0, 0, 474, 476, 7, 2, 0, 0, 475, 474, 1, 0, 0, 0, 475, 476, 1, 0, 0, 0, 476, 477, 1, 0, 0, 0, 477, 484, 5, 42, 0, 0, 478, 480, 5, 42, 0, 0, 479, 481, 7, 2, 0, 0, 480, 479, 1, 0, 0, 0, 480, 481, 1, 0, 0, 0, 481, 484, 1, 0, 0, 0, 482, 484, 7, 2, 0, 0, 483, 475, 1, 0, 0, 0, 483, 478, 1, 0, 0, 0, 483, 482, 1, 0, 0, 0, 484, 518, 1, 0, 0, 0, 485, 487, 7, 3, 0, 0, 486, 485, 1, 0, 0, 0, 486, 487, 1, 0, 0, 0, 487, 488, 1, 0, 0, 0, 488, 490, 7, 4, 0, 0, 489, 491, 5, 63, 0, 0, 490, 489, 1, 0, 0, 0, 490, 491, 1, 0, 0, 0, 491, 500, 1, 0, 0, 0, 492, 494, 7, 4, 0, 0, 493, 495, 5, 63, 0, 0, 494, 493, 1, 0, 0, 0, 494, 495, 1, 0, 0, 0, 495, 497, 1, 0, 0, 0, 496, 498, 7, 3, 0, 0, 497, 496, 1, 0, 0, 0, 497, 498, 1, 0, 0, 0, 498, 500, 1, 0, 0, 0, 499, 486, 1, 0, 0, 0, 499, 492, 1, 0, 0, 0, 500, 518, 1, 0, 0, 0, 501, 503, 7, 5, 0, 0, 502, 501, 1, 0, 0, 0, 502, 503, 1, 0, 0, 0, 503, 504, 1, 0, 0, 0, 504, 506, 5, 33, 0, 0, 505, 507, 5, 63, 0, 0, 506, 505, 1, 0, 0, 0, 506, 507, 1, 0, 0, 0, 507, 516, 1, 0, 0, 0, 508, 510, 5, 33, 0, 0, 509, 511, 5, 63, 0, 0, 510, 509, 1, 0, 0, 0, 510, 511, 1, 0, 0, 0, 511, 513, 1, 0, 0, 0, 512, 514, 7, 5, 0, 0, 513, 512, 1, 0, 0, 0, 513, 514, 1, 0, 0, 0, 514, 516, 1, 0, 0, 0, 515, 502, 1, 0, 0, 0, 515, 508, 1, 0, 0, 0, 516, 518, 1, 0, 0, 0, 517, 483, 1, 0, 0, 0, 517, 499, 1, 0, 0, 0, 517, 515, 1, 0, 0, 0, 518, 71, 1, 0, 0, 0, 519, 520, 5, 16, 0, 0, 520, 523, 5, 45, 0, 0, 521, 523, 5, 112, 0, 0, 522, 519, 1, 0, 0, 0, 522, 521, 1, 0, 0, 0, 523, 73, 1, 0, 0, 0, 524, 525, 5, 60, 0, 0, 525, 534, 3, 104, 52, 0, 526, 527, 5, 92, 0, 0, 527, 528, 5, 126, 0, 0, 528, 529, 3, 104, 52, 0, 529, 530, 5, 144, 0, 0, 530, 534, 1, 0, 0, 0, 531, 532, 5, 92, 0, 0, 532, 534, 3, 104, 52, 0, 533, 524, 1, 0, 0, 0, 533, 526, 1, 0, 0, 0, 533, 531, 1, 0, 0, 0, 534, 75, 1, 0, 0, 0, 535, 536, 5, 75, 0, 0, 536, 539, 3, 82, 41, 0, 537, 538, 5, 59, 0, 0, 538, 540, 3, 82, 41, 0, 539, 537, 1, 0, 0, 0, 539, 540, 1, 0, 0, 0, 540, 77, 1, 0, 0, 0, 541, 546, 3, 80, 40, 0, 542, 543, 5, 112, 0, 0, 543, 545, 3, 80, 40, 0, 544, 542, 1, 0, 0, 0, 545, 548, 1, 0, 0, 0, 546, 544, 1, 0, 0, 0, 546, 547, 1, 0, 0, 0, 547, 79, 1, 0, 0, 0, 548, 546, 1, 0, 0, 0, 549, 551, 3, 106, 53, 0, 550, 552, 7, 6, 0, 0, 551, 550, 1, 0, 0, 0, 551, 552, 1, 0, 0, 0, 552, 555, 1, 0, 0, 0, 553, 554, 5, 58, 0, 0, 554, 556, 7, 7, 0, 0, 555, 553, 1, 0, 0, 0, 555, 556, 1, 0, 0, 0, 556, 559, 1, 0, 0, 0, 557, 558, 5, 15, 0, 0, 558, 560, 5, 106, 0, 0, 559, 557, 1, 0, 0, 0, 559, 560, 1, 0, 0, 0, 560, 81, 1, 0, 0, 0, 561, 568, 3, 154, 77, 0, 562, 565, 3, 138, 69, 0, 563, 564, 5, 146, 0, 0, 564, 566, 3, 138, 69, 0, 565, 563, 1, 0, 0, 0, 565, 566, 1, 0, 0, 0, 566, 568, 1, 0, 0, 0, 567, 561, 1, 0, 0, 0, 567, 562, 1, 0, 0, 0, 568, 83, 1, 0, 0, 0, 569, 574, 3, 86, 43, 0, 570, 571, 5, 112, 0, 0, 571, 573, 3, 86, 43, 0, 572, 570, 1, 0, 0, 0, 573, 576, 1, 0, 0, 0, 574, 572, 1, 0, 0, 0, 574, 575, 1, 0, 0, 0, 575, 85, 1, 0, 0, 0, 576, 574, 1, 0, 0, 0, 577, 578, 3, 150, 75, 0, 578, 579, 5, 118, 0, 0, 579, 580, 3, 140, 70, 0, 580, 87, 1, 0, 0, 0, 581, 583, 3, 90, 45, 0, 582, 581, 1, 0, 0, 0, 582, 583, 1, 0, 0, 0, 583, 585, 1, 0, 0, 0, 584, 586, 3, 92, 46, 0, 585, 584, 1, 0, 0, 0, 585, 586, 1, 0, 0, 0, 586, 588, 1, 0, 0, 0, 587, 589, 3, 94, 47, 0, 588, 587, 1, 0, 0, 0, 588, 589, 1, 0, 0, 0, 589, 89, 1, 0, 0, 0, 590, 591, 5, 65, 0, 0, 591, 592, 5, 11, 0, 0, 592, 593, 3, 104, 52, 0, 593, 91, 1, 0, 0, 0, 594, 595, 5, 62, 0, 0, 595, 596, 5, 11, 0, 0, 596, 597, 3, 78, 39, 0, 597, 93, 1, 0, 0, 0, 598, 599, 7, 8, 0, 0, 599, 600, 3, 96, 48, 0, 600, 95, 1, 0, 0, 0, 601, 608, 3, 98, 49, 0, 602, 603, 5, 9, 0, 0, 603, 604, 3, 98, 49, 0, 604, 605, 5, 2, 0, 0, 605, 606, 3, 98, 49, 0, 606, 608, 1, 0, 0, 0, 607, 601, 1, 0, 0, 0, 607, 602, 1, 0, 0, 0, 608, 97, 1, 0, 0, 0, 609, 610, 5, 18, 0, 0, 610, 622, 5, 73, 0, 0, 611, 612, 5, 90, 0, 0, 612, 622, 5, 66, 0, 0, 613, 614, 5, 90, 0, 0, 614, 622, 5, 30, 0, 0, 615, 616, 3, 138, 69, 0, 616, 617, 5, 66, 0, 0, 617, 622, 1, 0, 0, 0, 618, 619, 3, 138, 69, 0, 619, 620, 5, 30, 0, 0, 620, 622, 1, 0, 0, 0, 621, 609, 1, 0, 0, 0, 621, 611, 1, 0, 0, 0, 621, 613, 1, 0, 0, 0, 621, 615, 1, 0, 0, 0, 621, 618, 1, 0, 0, 0, 622, 99, 1, 0, 0, 0, 623, 624, 3, 106, 53, 0, 624, 625, 5, 0, 0, 1, 625, 101, 1, 0, 0, 0, 626, 674, 3, 150, 75, 0, 627, 628, 3, 150, 75, 0, 628, 629, 5, 126, 0, 0, 629, 630, 3, 150, 75, 0, 630, 637, 3, 102, 51, 0, 631, 632, 5, 112, 0, 0, 632, 633, 3, 150, 75, 0, 633, 634, 3, 102, 51, 0, 634, 636, 1, 0, 0, 0, 635, 631, 1, 0, 0, 0, 636, 639, 1, 0, 0, 0, 637, 635, 1, 0, 0, 0, 637, 638, 1, 0, 0, 0, 638, 640, 1, 0, 0, 0, 639, 637, 1, 0, 0, 0, 640, 641, 5, 144, 0, 0, 641, 674, 1, 0, 0, 0, 642, 643, 3, 150, 75, 0, 643, 644, 5, 126, 0, 0, 644, 649, 3, 152, 76, 0, 645, 646, 5, 112, 0, 0, 646, 648, 3, 152, 76, 0, 647, 645, 1, 0, 0, 0, 648, 651, 1, 0, 0, 0, 649, 647, 1, 0, 0, 0, 649, 650, 1, 0, 0, 0, 650, 652, 1, 0, 0, 0, 651, 649, 1, 0, 0, 0, 652, 653, 5, 144, 0, 0, 653, 674, 1, 0, 0, 0, 654, 655, 3, 150, 75, 0, 655, 656, 5, 126, 0, 0, 656, 661, 3, 102, 51, 0, 657, 658, 5, 112, 0, 0, 658, 660, 3, 102, 51, 0, 659, 657, 1, 0, 0, 0, 660, 663, 1, 0, 0, 0, 661, 659, 1, 0, 0, 0, 661, 662, 1, 0, 0, 0, 662, 664, 1, 0, 0, 0, 663, 661, 1, 0, 0, 0, 664, 665, 5, 144, 0, 0, 665, 674, 1, 0, 0, 0, 666, 667, 3, 150, 75, 0, 667, 669, 5, 126, 0, 0, 668, 670, 3, 104, 52, 0, 669, 668, 1, 0, 0, 0, 669, 670, 1, 0, 0, 0, 670, 671, 1, 0, 0, 0, 671, 672, 5, 144, 0, 0, 672, 674, 1, 0, 0, 0, 673, 626, 1, 0, 0, 0, 673, 627, 1, 0, 0, 0, 673, 642, 1, 0, 0, 0, 673, 654, 1, 0, 0, 0, 673, 666, 1, 0, 0, 0, 674, 103, 1, 0, 0, 0, 675, 680, 3, 106, 53, 0, 676, 677, 5, 112, 0, 0, 677, 679, 3, 106, 53, 0, 678, 676, 1, 0, 0, 0, 679, 682, 1, 0, 0, 0, 680, 678, 1, 0, 0, 0, 680, 681, 1, 0, 0, 0, 681, 105, 1, 0, 0, 0, 682, 680, 1, 0, 0, 0, 683, 684, 6, 53, -1, 0, 684, 686, 5, 12, 0, 0, 685, 687, 3, 106, 53, 0, 686, 685, 1, 0, 0, 0, 686, 687, 1, 0, 0, 0, 687, 693, 1, 0, 0, 0, 688, 689, 5, 94, 0, 0, 689, 690, 3, 106, 53, 0, 690, 691, 5, 81, 0, 0, 691, 692, 3, 106, 53, 0, 692, 694, 1, 0, 0, 0, 693, 688, 1, 0, 0, 0, 694, 695, 1, 0, 0, 0, 695, 693, 1, 0, 0, 0, 695, 696, 1, 0, 0, 0, 696, 699, 1, 0, 0, 0, 697, 698, 5, 24, 0, 0, 698, 700, 3, 106, 53, 0, 699, 697, 1, 0, 0, 0, 699, 700, 1, 0, 0, 0, 700, 701, 1, 0, 0, 0, 701, 702, 5, 25, 0, 0, 702, 813, 1, 0, 0, 0, 703, 704, 5, 13, 0, 0, 704, 705, 5, 126, 0, 0, 705, 706, 3, 106, 53, 0, 706, 707, 5, 6, 0, 0, 707, 708, 3, 102, 51, 0, 708, 709, 5, 144, 0, 0, 709, 813, 1, 0, 0, 0, 710, 711, 5, 19, 0, 0, 711, 813, 5, 106, 0, 0, 712, 713, 5, 43, 0, 0, 713, 714, 3, 106, 53, 0, 714, 715, 3, 142, 71, 0, 715, 813, 1, 0, 0, 0, 716, 717, 5, 80, 0, 0, 717, 718, 5, 126, 0, 0, 718, 719, 3, 106, 53, 0, 719, 720, 5, 32, 0, 0, 720, 723, 3, 106, 53, 0, 721, 722, 5, 31, 0, 0, 722, 724, 3, 106, 53, 0, 723, 721, 1, 0, 0, 0, 723, 724, 1, 0, 0, 0, 724, 725, 1, 0, 0, 0, 725, 726, 5, 144, 0, 0, 726, 813, 1, 0, 0, 0, 727, 728, 5, 83, 0, 0, 728, 813, 5, 106, 0, 0, 729, 730, 5, 88, 0, 0, 730, 731, 5, 126, 0, 0, 731, 732, 7, 9, 0, 0, 732, 733, 3, 156, 78, 0, 733, 734, 5, 32, 0, 0, 734, 735, 3, 106, 53, 0, 735, 736, 5, 144, 0, 0, 736, 813, 1, 0, 0, 0, 737, 738, 3, 150, 75, 0, 738, 740, 5, 126, 0, 0, 739, 741, 3, 104, 52, 0, 740, 739, 1, 0, 0, 0, 740, 741, 1, 0, 0, 0, 741, 742, 1, 0, 0, 0, 742, 743, 5, 144, 0, 0, 743, 744, 1, 0, 0, 0, 744, 745, 5, 64, 0, 0, 745, 746, 5, 126, 0, 0, 746, 747, 3, 88, 44, 0, 747, 748, 5, 144, 0, 0, 748, 813, 1, 0, 0, 0, 749, 750, 3, 150, 75, 0, 750, 752, 5, 126, 0, 0, 751, 753, 3, 104, 52, 0, 752, 751, 1, 0, 0, 0, 752, 753, 1, 0, 0, 0, 753, 754, 1, 0, 0, 0, 754, 755, 5, 144, 0, 0, 755, 756, 1, 0, 0, 0, 756, 757, 5, 64, 0, 0, 757, 758, 3, 150, 75, 0, 758, 813, 1, 0, 0, 0, 759, 765, 3, 150, 75, 0, 760, 762, 5, 126, 0, 0, 761, 763, 3, 104, 52, 0, 762, 761, 1, 0, 0, 0, 762, 763, 1, 0, 0, 0, 763, 764, 1, 0, 0, 0, 764, 766, 5, 144, 0, 0, 765, 760, 1, 0, 0, 0, 765, 766, 1, 0, 0, 0, 766, 767, 1, 0, 0, 0, 767, 769, 5, 126, 0, 0, 768, 770, 5, 23, 0, 0, 769, 768, 1, 0, 0, 0, 769, 770, 1, 0, 0, 0, 770, 772, 1, 0, 0, 0, 771, 773, 3, 108, 54, 0, 772, 771, 1, 0, 0, 0, 772, 773, 1, 0, 0, 0, 773, 774, 1, 0, 0, 0, 774, 775, 5, 144, 0, 0, 775, 813, 1, 0, 0, 0, 776, 813, 3, 114, 57, 0, 777, 813, 3, 158, 79, 0, 778, 813, 3, 140, 70, 0, 779, 780, 5, 114, 0, 0, 780, 813, 3, 106, 53, 19, 781, 782, 5, 56, 0, 0, 782, 813, 3, 106, 53, 13, 783, 784, 3, 130, 65, 0, 784, 785, 5, 116, 0, 0, 785, 787, 1, 0, 0, 0, 786, 783, 1, 0, 0, 0, 786, 787, 1, 0, 0, 0, 787, 788, 1, 0, 0, 0, 788, 813, 5, 108, 0, 0, 789, 790, 5, 126, 0, 0, 790, 791, 3, 34, 17, 0, 791, 792, 5, 144, 0, 0, 792, 813, 1, 0, 0, 0, 793, 794, 5, 126, 0, 0, 794, 795, 3, 106, 53, 0, 795, 796, 5, 144, 0, 0, 796, 813, 1, 0, 0, 0, 797, 798, 5, 126, 0, 0, 798, 799, 3, 104, 52, 0, 799, 800, 5, 144, 0, 0, 800, 813, 1, 0, 0, 0, 801, 803, 5, 125, 0, 0, 802, 804, 3, 104, 52, 0, 803, 802, 1, 0, 0, 0, 803, 804, 1, 0, 0, 0, 804, 805, 1, 0, 0, 0, 805, 813, 5, 143, 0, 0, 806, 808, 5, 124, 0, 0, 807, 809, 3, 30, 15, 0, 808, 807, 1, 0, 0, 0, 808, 809, 1, 0, 0, 0, 809, 810, 1, 0, 0, 0, 810, 813, 5, 142, 0, 0, 811, 813, 3, 122, 61, 0, 812, 683, 1, 0, 0, 0, 812, 703, 1, 0, 0, 0, 812, 710, 1, 0, 0, 0, 812, 712, 1, 0, 0, 0, 812, 716, 1, 0, 0, 0, 812, 727, 1, 0, 0, 0, 812, 729, 1, 0, 0, 0, 812, 737, 1, 0, 0, 0, 812, 749, 1, 0, 0, 0, 812, 759, 1, 0, 0, 0, 812, 776, 1, 0, 0, 0, 812, 777, 1, 0, 0, 0, 812, 778, 1, 0, 0, 0, 812, 779, 1, 0, 0, 0, 812, 781, 1, 0, 0, 0, 812, 786, 1, 0, 0, 0, 812, 789, 1, 0, 0, 0, 812, 793, 1, 0, 0, 0, 812, 797, 1, 0, 0, 0, 812, 801, 1, 0, 0, 0, 812, 806, 1, 0, 0, 0, 812, 811, 1, 0, 0, 0, 813, 907, 1, 0, 0, 0, 814, 818, 10, 18, 0, 0, 815, 819, 5, 108, 0, 0, 816, 819, 5, 146, 0, 0, 817, 819, 5, 133, 0, 0, 818, 815, 1, 0, 0, 0, 818, 816, 1, 0, 0, 0, 818, 817, 1, 0, 0, 0, 819, 820, 1, 0, 0, 0, 820, 906, 3, 106, 53, 19, 821, 825, 10, 17, 0, 0, 822, 826, 5, 134, 0, 0, 823, 826, 5, 114, 0, 0, 824, 826, 5, 113, 0, 0, 825, 822, 1, 0, 0, 0, 825, 823, 1, 0, 0, 0, 825, 824, 1, 0, 0, 0, 826, 827, 1, 0, 0, 0, 827, 906, 3, 106, 53, 18, 828, 853, 10, 16, 0, 0, 829, 854, 5, 117, 0, 0, 830, 854, 5, 118, 0, 0, 831, 854, 5, 129, 0, 0, 832, 854, 5, 127, 0, 0, 833, 854, 5, 128, 0, 0, 834, 854, 5, 119, 0, 0, 835, 854, 5, 120, 0, 0, 836, 838, 5, 56, 0, 0, 837, 836, 1, 0, 0, 0, 837, 838, 1, 0, 0, 0, 838, 839, 1, 0, 0, 0, 839, 841, 5, 40, 0, 0, 840, 842, 5, 14, 0, 0, 841, 840, 1, 0, 0, 0, 841, 842, 1, 0, 0, 0, 842, 854, 1, 0, 0, 0, 843, 845, 5, 56, 0, 0, 844, 843, 1, 0, 0, 0, 844, 845, 1, 0, 0, 0, 845, 846, 1, 0, 0, 0, 846, 854, 7, 10, 0, 0, 847, 854, 5, 140, 0, 0, 848, 854, 5, 141, 0, 0, 849, 854, 5, 131, 0, 0, 850, 854, 5, 122, 0, 0, 851, 854, 5, 123, 0, 0, 852, 854, 5, 130, 0, 0, 853, 829, 1, 0, 0, 0, 853, 830, 1, 0, 0, 0, 853, 831, 1, 0, 0, 0, 853, 832, 1, 0, 0, 0, 853, 833, 1, 0, 0, 0, 853, 834, 1, 0, 0, 0, 853, 835, 1, 0, 0, 0, 853, 837, 1, 0, 0, 0, 853, 844, 1, 0, 0, 0, 853, 847, 1, 0, 0, 0, 853, 848, 1, 0, 0, 0, 853, 849, 1, 0, 0, 0, 853, 850, 1, 0, 0, 0, 853, 851, 1, 0, 0, 0, 853, 852, 1, 0, 0, 0, 854, 855, 1, 0, 0, 0, 855, 906, 3, 106, 53, 17, 856, 857, 10, 14, 0, 0, 857, 858, 5, 132, 0, 0, 858, 906, 3, 106, 53, 15, 859, 860, 10, 12, 0, 0, 860, 861, 5, 2, 0, 0, 861, 906, 3, 106, 53, 13, 862, 863, 10, 11, 0, 0, 863, 864, 5, 61, 0, 0, 864, 906, 3, 106, 53, 12, 865, 867, 10, 10, 0, 0, 866, 868, 5, 56, 0, 0, 867, 866, 1, 0, 0, 0, 867, 868, 1, 0, 0, 0, 868, 869, 1, 0, 0, 0, 869, 870, 5, 9, 0, 0, 870, 871, 3, 106, 53, 0, 871, 872, 5, 2, 0, 0, 872, 873, 3, 106, 53, 11, 873, 906, 1, 0, 0, 0, 874, 875, 10, 9, 0, 0, 875, 876, 5, 135, 0, 0, 876, 877, 3, 106, 53, 0, 877, 878, 5, 111, 0, 0, 878, 879, 3, 106, 53, 9, 879, 906, 1, 0, 0, 0, 880, 881, 10, 22, 0, 0, 881, 882, 5, 125, 0, 0, 882, 883, 3, 106, 53, 0, 883, 884, 5, 143, 0, 0, 884, 906, 1, 0, 0, 0, 885, 886, 10, 21, 0, 0, 886, 887, 5, 116, 0, 0, 887, 906, 5, 104, 0, 0, 888, 889, 10, 20, 0, 0, 889, 890, 5, 116, 0, 0, 890, 906, 3, 150, 75, 0, 891, 892, 10, 15, 0, 0, 892, 894, 5, 44, 0, 0, 893, 895, 5, 56, 0, 0, 894, 893, 1, 0, 0, 0, 894, 895, 1, 0, 0, 0, 895, 896, 1, 0, 0, 0, 896, 906, 5, 57, 0, 0, 897, 903, 10, 8, 0, 0, 898, 904, 3, 148, 74, 0, 899, 900, 5, 6, 0, 0, 900, 904, 3, 150, 75, 0, 901, 902, 5, 6, 0, 0, 902, 904, 5, 106, 0, 0, 903, 898, 1, 0, 0, 0, 903, 899, 1, 0, 0, 0, 903, 901, 1, 0, 0, 0, 904, 906, 1, 0, 0, 0, 905, 814, 1, 0, 0, 0, 905, 821, 1, 0, 0, 0, 905, 828, 1, 0, 0, 0, 905, 856, 1, 0, 0, 0, 905, 859, 1, 0, 0, 0, 905, 862, 1, 0, 0, 0, 905, 865, 1, 0, 0, 0, 905, 874, 1, 0, 0, 0, 905, 880, 1, 0, 0, 0, 905, 885, 1, 0, 0, 0, 905, 888, 1, 0, 0, 0, 905, 891, 1, 0, 0, 0, 905, 897, 1, 0, 0, 0, 906, 909, 1, 0, 0, 0, 907, 905, 1, 0, 0, 0, 907, 908, 1, 0, 0, 0, 908, 107, 1, 0, 0, 0, 909, 907, 1, 0, 0, 0, 910, 915, 3, 110, 55, 0, 911, 912, 5, 112, 0, 0, 912, 914, 3, 110, 55, 0, 913, 911, 1, 0, 0, 0, 914, 917, 1, 0, 0, 0, 915, 913, 1, 0, 0, 0, 915, 916, 1, 0, 0, 0, 916, 109, 1, 0, 0, 0, 917, 915, 1, 0, 0, 0, 918, 921, 3, 112, 56, 0, 919, 921, 3, 106, 53, 0, 920, 918, 1, 0, 0, 0, 920, 919, 1, 0, 0, 0, 921, 111, 1, 0, 0, 0, 922, 923, 5, 126, 0, 0, 923, 928, 3, 150, 75, 0, 924, 925, 5, 112, 0, 0, 925, 927, 3, 150, 75, 0, 926, 924, 1, 0, 0, 0, 927, 930, 1, 0, 0, 0, 928, 926, 1, 0, 0, 0, 928, 929, 1, 0, 0, 0, 929, 931, 1, 0, 0, 0, 930, 928, 1, 0, 0, 0, 931, 932, 5, 144, 0, 0, 932, 942, 1, 0, 0, 0, 933, 938, 3, 150, 75, 0, 934, 935, 5, 112, 0, 0, 935, 937, 3, 150, 75, 0, 936, 934, 1, 0, 0, 0, 937, 940, 1, 0, 0, 0, 938, 936, 1, 0, 0, 0, 938, 939, 1, 0, 0, 0, 939, 942, 1, 0, 0, 0, 940, 938, 1, 0, 0, 0, 941, 922, 1, 0, 0, 0, 941, 933, 1, 0, 0, 0, 942, 943, 1, 0, 0, 0, 943, 944, 5, 107, 0, 0, 944, 945, 3, 106, 53, 0, 945, 113, 1, 0, 0, 0, 946, 947, 5, 128, 0, 0, 947, 951, 3, 150, 75, 0, 948, 950, 3, 116, 58, 0, 949, 948, 1, 0, 0, 0, 950, 953, 1, 0, 0, 0, 951, 949, 1, 0, 0, 0, 951, 952, 1, 0, 0, 0, 952, 954, 1, 0, 0, 0, 953, 951, 1, 0, 0, 0, 954, 955, 5, 146, 0, 0, 955, 956, 5, 120, 0, 0, 956, 975, 1, 0, 0, 0, 957, 958, 5, 128, 0, 0, 958, 962, 3, 150, 75, 0, 959, 961, 3, 116, 58, 0, 960, 959, 1, 0, 0, 0, 961, 964, 1, 0, 0, 0, 962, 960, 1, 0, 0, 0, 962, 963, 1, 0, 0, 0, 963, 965, 1, 0, 0, 0, 964, 962, 1, 0, 0, 0, 965, 967, 5, 120, 0, 0, 966, 968, 3, 114, 57, 0, 967, 966, 1, 0, 0, 0, 967, 968, 1, 0, 0, 0, 968, 969, 1, 0, 0, 0, 969, 970, 5, 128, 0, 0, 970, 971, 5, 146, 0, 0, 971, 972, 3, 150, 75, 0, 972, 973, 5, 120, 0, 0, 973, 975, 1, 0, 0, 0, 974, 946, 1, 0, 0, 0, 974, 957, 1, 0, 0, 0, 975, 115, 1, 0, 0, 0, 976, 977, 3, 150, 75, 0, 977, 978, 5, 118, 0, 0, 978, 979, 3, 156, 78, 0, 979, 988, 1, 0, 0, 0, 980, 981, 3, 150, 75, 0, 981, 982, 5, 118, 0, 0, 982, 983, 5, 124, 0, 0, 983, 984, 3, 106, 53, 0, 984, 985, 5, 142, 0, 0, 985, 988, 1, 0, 0, 0, 986, 988, 3, 150, 75, 0, 987, 976, 1, 0, 0, 0, 987, 980, 1, 0, 0, 0, 987, 986, 1, 0, 0, 0, 988, 117, 1, 0, 0, 0, 989, 994, 3, 120, 60, 0, 990, 991, 5, 112, 0, 0, 991, 993, 3, 120, 60, 0, 992, 990, 1, 0, 0, 0, 993, 996, 1, 0, 0, 0, 994, 992, 1, 0, 0, 0, 994, 995, 1, 0, 0, 0, 995, 119, 1, 0, 0, 0, 996, 994, 1, 0, 0, 0, 997, 998, 3, 150, 75, 0, 998, 999, 5, 6, 0, 0, 999, 1000, 5, 126, 0, 0, 1000, 1001, 3, 34, 17, 0, 1001, 1002, 5, 144, 0, 0, 1002, 1008, 1, 0, 0, 0, 1003, 1004, 3, 106, 53, 0, 1004, 1005, 5, 6, 0, 0, 1005, 1006, 3, 150, 75, 0, 1006, 1008, 1, 0, 0, 0, 1007, 997, 1, 0, 0, 0, 1007, 1003, 1, 0, 0, 0, 1008, 121, 1, 0, 0, 0, 1009, 1017, 3, 154, 77, 0, 1010, 1011, 3, 130, 65, 0, 1011, 1012, 5, 116, 0, 0, 1012, 1014, 1, 0, 0, 0, 1013, 1010, 1, 0, 0, 0, 1013, 1014, 1, 0, 0, 0, 1014, 1015, 1, 0, 0, 0, 1015, 1017, 3, 124, 62, 0, 1016, 1009, 1, 0, 0, 0, 1016, 1013, 1, 0, 0, 0, 1017, 123, 1, 0, 0, 0, 1018, 1023, 3, 150, 75, 0, 1019, 1020, 5, 116, 0, 0, 1020, 1022, 3, 150, 75, 0, 1021, 1019, 1, 0, 0, 0, 1022, 1025, 1, 0, 0, 0, 1023, 1021, 1, 0, 0, 0, 1023, 1024, 1, 0, 0, 0, 1024, 125, 1, 0, 0, 0, 1025, 1023, 1, 0, 0, 0, 1026, 1027, 6, 63, -1, 0, 1027, 1036, 3, 130, 65, 0, 1028, 1036, 3, 128, 64, 0, 1029, 1030, 5, 126, 0, 0, 1030, 1031, 3, 34, 17, 0, 1031, 1032, 5, 144, 0, 0, 1032, 1036, 1, 0, 0, 0, 1033, 1036, 3, 114, 57, 0, 1034, 1036, 3, 154, 77, 0, 1035, 1026, 1, 0, 0, 0, 1035, 1028, 1, 0, 0, 0, 1035, 1029, 1, 0, 0, 0, 1035, 1033, 1, 0, 0, 0, 1035, 1034, 1, 0, 0, 0, 1036, 1045, 1, 0, 0, 0, 1037, 1041, 10, 3, 0, 0, 1038, 1042, 3, 148, 74, 0, 1039, 1040, 5, 6, 0, 0, 1040, 1042, 3, 150, 75, 0, 1041, 1038, 1, 0, 0, 0, 1041, 1039, 1, 0, 0, 0, 1042, 1044, 1, 0, 0, 0, 1043, 1037, 1, 0, 0, 0, 1044, 1047, 1, 0, 0, 0, 1045, 1043, 1, 0, 0, 0, 1045, 1046, 1, 0, 0, 0, 1046, 127, 1, 0, 0, 0, 1047, 1045, 1, 0, 0, 0, 1048, 1049, 3, 150, 75, 0, 1049, 1051, 5, 126, 0, 0, 1050, 1052, 3, 132, 66, 0, 1051, 1050, 1, 0, 0, 0, 1051, 1052, 1, 0, 0, 0, 1052, 1053, 1, 0, 0, 0, 1053, 1054, 5, 144, 0, 0, 1054, 129, 1, 0, 0, 0, 1055, 1056, 3, 134, 67, 0, 1056, 1057, 5, 116, 0, 0, 1057, 1059, 1, 0, 0, 0, 1058, 1055, 1, 0, 0, 0, 1058, 1059, 1, 0, 0, 0, 1059, 1060, 1, 0, 0, 0, 1060, 1061, 3, 150, 75, 0, 1061, 131, 1, 0, 0, 0, 1062, 1067, 3, 106, 53, 0, 1063, 1064, 5, 112, 0, 0, 1064, 1066, 3, 106, 53, 0, 1065, 1063, 1, 0, 0, 0, 1066, 1069, 1, 0, 0, 0, 1067, 1065, 1, 0, 0, 0, 1067, 1068, 1, 0, 0, 0, 1068, 133, 1, 0, 0, 0, 1069, 1067, 1, 0, 0, 0, 1070, 1071, 3, 150, 75, 0, 1071, 135, 1, 0, 0, 0, 1072, 1081, 5, 102, 0, 0, 1073, 1074, 5, 116, 0, 0, 1074, 1081, 7, 11, 0, 0, 1075, 1076, 5, 104, 0, 0, 1076, 1078, 5, 116, 0, 0, 1077, 1079, 7, 11, 0, 0, 1078, 1077, 1, 0, 0, 0, 1078, 1079, 1, 0, 0, 0, 1079, 1081, 1, 0, 0, 0, 1080, 1072, 1, 0, 0, 0, 1080, 1073, 1, 0, 0, 0, 1080, 1075, 1, 0, 0, 0, 1081, 137, 1, 0, 0, 0, 1082, 1084, 7, 12, 0, 0, 1083, 1082, 1, 0, 0, 0, 1083, 1084, 1, 0, 0, 0, 1084, 1091, 1, 0, 0, 0, 1085, 1092, 3, 136, 68, 0, 1086, 1092, 5, 103, 0, 0, 1087, 1092, 5, 104, 0, 0, 1088, 1092, 5, 105, 0, 0, 1089, 1092, 5, 41, 0, 0, 1090, 1092, 5, 55, 0, 0, 1091, 1085, 1, 0, 0, 0, 1091, 1086, 1, 0, 0, 0, 1091, 1087, 1, 0, 0, 0, 1091, 1088, 1, 0, 0, 0, 1091, 1089, 1, 0, 0, 0, 1091, 1090, 1, 0, 0, 0, 1092, 139, 1, 0, 0, 0, 1093, 1097, 3, 138, 69, 0, 1094, 1097, 5, 106, 0, 0, 1095, 1097, 5, 57, 0, 0, 1096, 1093, 1, 0, 0, 0, 1096, 1094, 1, 0, 0, 0, 1096, 1095, 1, 0, 0, 0, 1097, 141, 1, 0, 0, 0, 1098, 1099, 7, 13, 0, 0, 1099, 143, 1, 0, 0, 0, 1100, 1101, 7, 14, 0, 0, 1101, 145, 1, 0, 0, 0, 1102, 1103, 7, 15, 0, 0, 1103, 147, 1, 0, 0, 0, 1104, 1107, 5, 101, 0, 0, 1105, 1107, 3, 146, 73, 0, 1106, 1104, 1, 0, 0, 0, 1106, 1105, 1, 0, 0, 0, 1107, 149, 1, 0, 0, 0, 1108, 1112, 5, 101, 0, 0, 1109, 1112, 3, 142, 71, 0, 1110, 1112, 3, 144, 72, 0, 1111, 1108, 1, 0, 0, 0, 1111, 1109, 1, 0, 0, 0, 1111, 1110, 1, 0, 0, 0, 1112, 151, 1, 0, 0, 0, 1113, 1114, 3, 156, 78, 0, 1114, 1115, 5, 118, 0, 0, 1115, 1116, 3, 138, 69, 0, 1116, 153, 1, 0, 0, 0, 1117, 1118, 5, 124, 0, 0, 1118, 1119, 3, 150, 75, 0, 1119, 1120, 5, 142, 0, 0, 1120, 155, 1, 0, 0, 0, 1121, 1124, 5, 106, 0, 0, 1122, 1124, 3, 158, 79, 0, 1123, 1121, 1, 0, 0, 0, 1123, 1122, 1, 0, 0, 0, 1124, 157, 1, 0, 0, 0, 1125, 1129, 5, 137, 0, 0, 1126, 1128, 3, 160, 80, 0, 1127, 1126, 1, 0, 0, 0, 1128, 1131, 1, 0, 0, 0, 1129, 1127, 1, 0, 0, 0, 1129, 1130, 1, 0, 0, 0, 1130, 1132, 1, 0, 0, 0, 1131, 1129, 1, 0, 0, 0, 1132, 1133, 5, 139, 0, 0, 1133, 159, 1, 0, 0, 0, 1134, 1135, 5, 152, 0, 0, 1135, 1136, 3, 106, 53, 0, 1136, 1137, 5, 142, 0, 0, 1137, 1140, 1, 0, 0, 0, 1138, 1140, 5, 151, 0, 0, 1139, 1134, 1, 0, 0, 0, 1139, 1138, 1, 0, 0, 0, 1140, 161, 1, 0, 0, 0, 1141, 1145, 5, 138, 0, 0, 1142, 1144, 3, 164, 82, 0, 1143, 1142, 1, 0, 0, 0, 1144, 1147, 1, 0, 0, 0, 1145, 1143, 1, 0, 0, 0, 1145, 1146, 1, 0, 0, 0, 1146, 1148, 1, 0, 0, 0, 1147, 1145, 1, 0, 0, 0, 1148, 1149, 5, 0, 0, 1, 1149, 163, 1, 0, 0, 0, 1150, 1151, 5, 154, 0, 0, 1151, 1152, 3, 106, 53, 0, 1152, 1153, 5, 142, 0, 0, 1153, 1156, 1, 0, 0, 0, 1154, 1156, 5, 153, 0, 0, 1155, 1150, 1, 0, 0, 0, 1155, 1154, 1, 0, 0, 0, 1156, 165, 1, 0, 0, 0, 135, 169, 176, 185, 200, 212, 224, 240, 251, 265, 271, 281, 290, 293, 297, 300, 304, 307, 310, 313, 316, 320, 324, 327, 330, 333, 337, 340, 349, 355, 376, 393, 410, 416, 422, 433, 435, 446, 449, 455, 463, 469, 471, 475, 480, 483, 486, 490, 494, 497, 499, 502, 506, 510, 513, 515, 517, 522, 533, 539, 546, 551, 555, 559, 565, 567, 574, 582, 585, 588, 607, 621, 637, 649, 661, 669, 673, 680, 686, 695, 699, 723, 740, 752, 762, 765, 769, 772, 786, 803, 808, 812, 818, 825, 837, 841, 844, 853, 867, 894, 903, 905, 907, 915, 920, 928, 938, 941, 951, 962, 967, 974, 987, 994, 1007, 1013, 1016, 1023, 1035, 1041, 1045, 1051, 1058, 1067, 1078, 1080, 1083, 1091, 1096, 1106, 1111, 1123, 1129, 1139, 1145, 1155] \ No newline at end of file +[4, 1, 154, 1178, 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, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, 41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, 2, 47, 7, 47, 2, 48, 7, 48, 2, 49, 7, 49, 2, 50, 7, 50, 2, 51, 7, 51, 2, 52, 7, 52, 2, 53, 7, 53, 2, 54, 7, 54, 2, 55, 7, 55, 2, 56, 7, 56, 2, 57, 7, 57, 2, 58, 7, 58, 2, 59, 7, 59, 2, 60, 7, 60, 2, 61, 7, 61, 2, 62, 7, 62, 2, 63, 7, 63, 2, 64, 7, 64, 2, 65, 7, 65, 2, 66, 7, 66, 2, 67, 7, 67, 2, 68, 7, 68, 2, 69, 7, 69, 2, 70, 7, 70, 2, 71, 7, 71, 2, 72, 7, 72, 2, 73, 7, 73, 2, 74, 7, 74, 2, 75, 7, 75, 2, 76, 7, 76, 2, 77, 7, 77, 2, 78, 7, 78, 2, 79, 7, 79, 2, 80, 7, 80, 2, 81, 7, 81, 2, 82, 7, 82, 1, 0, 5, 0, 168, 8, 0, 10, 0, 12, 0, 171, 9, 0, 1, 0, 1, 0, 1, 1, 1, 1, 3, 1, 177, 8, 1, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 3, 3, 186, 8, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 5, 5, 199, 8, 5, 10, 5, 12, 5, 202, 9, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 3, 6, 213, 8, 6, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 3, 8, 225, 8, 8, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, 1, 11, 3, 11, 241, 8, 11, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 13, 1, 13, 5, 13, 250, 8, 13, 10, 13, 12, 13, 253, 9, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 1, 14, 1, 15, 1, 15, 1, 15, 5, 15, 264, 8, 15, 10, 15, 12, 15, 267, 9, 15, 1, 16, 1, 16, 1, 16, 3, 16, 272, 8, 16, 1, 16, 1, 16, 1, 17, 1, 17, 1, 17, 1, 17, 5, 17, 280, 8, 17, 10, 17, 12, 17, 283, 9, 17, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 3, 18, 291, 8, 18, 1, 19, 3, 19, 294, 8, 19, 1, 19, 1, 19, 3, 19, 298, 8, 19, 1, 19, 3, 19, 301, 8, 19, 1, 19, 1, 19, 3, 19, 305, 8, 19, 1, 19, 3, 19, 308, 8, 19, 1, 19, 3, 19, 311, 8, 19, 1, 19, 3, 19, 314, 8, 19, 1, 19, 3, 19, 317, 8, 19, 1, 19, 1, 19, 3, 19, 321, 8, 19, 1, 19, 1, 19, 3, 19, 325, 8, 19, 1, 19, 3, 19, 328, 8, 19, 1, 19, 3, 19, 331, 8, 19, 1, 19, 3, 19, 334, 8, 19, 1, 19, 1, 19, 3, 19, 338, 8, 19, 1, 19, 3, 19, 341, 8, 19, 1, 20, 1, 20, 1, 20, 1, 21, 1, 21, 1, 21, 1, 21, 3, 21, 350, 8, 21, 1, 22, 1, 22, 1, 22, 1, 23, 3, 23, 356, 8, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 5, 24, 375, 8, 24, 10, 24, 12, 24, 378, 9, 24, 1, 25, 1, 25, 1, 25, 1, 26, 1, 26, 1, 26, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 3, 27, 394, 8, 27, 1, 28, 1, 28, 1, 28, 1, 29, 1, 29, 1, 29, 1, 29, 1, 30, 1, 30, 1, 30, 1, 30, 1, 31, 1, 31, 1, 31, 1, 31, 3, 31, 411, 8, 31, 1, 31, 1, 31, 1, 31, 1, 31, 3, 31, 417, 8, 31, 1, 31, 1, 31, 1, 31, 1, 31, 3, 31, 423, 8, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 3, 31, 434, 8, 31, 3, 31, 436, 8, 31, 1, 32, 1, 32, 1, 32, 1, 33, 1, 33, 1, 33, 1, 34, 1, 34, 1, 34, 3, 34, 447, 8, 34, 1, 34, 3, 34, 450, 8, 34, 1, 34, 1, 34, 1, 34, 1, 34, 3, 34, 456, 8, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 3, 34, 464, 8, 34, 1, 34, 1, 34, 1, 34, 1, 34, 5, 34, 470, 8, 34, 10, 34, 12, 34, 473, 9, 34, 1, 35, 3, 35, 476, 8, 35, 1, 35, 1, 35, 1, 35, 3, 35, 481, 8, 35, 1, 35, 3, 35, 484, 8, 35, 1, 35, 3, 35, 487, 8, 35, 1, 35, 1, 35, 3, 35, 491, 8, 35, 1, 35, 1, 35, 3, 35, 495, 8, 35, 1, 35, 3, 35, 498, 8, 35, 3, 35, 500, 8, 35, 1, 35, 3, 35, 503, 8, 35, 1, 35, 1, 35, 3, 35, 507, 8, 35, 1, 35, 1, 35, 3, 35, 511, 8, 35, 1, 35, 3, 35, 514, 8, 35, 3, 35, 516, 8, 35, 3, 35, 518, 8, 35, 1, 36, 1, 36, 1, 36, 3, 36, 523, 8, 36, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 3, 37, 534, 8, 37, 1, 38, 1, 38, 1, 38, 1, 38, 3, 38, 540, 8, 38, 1, 39, 1, 39, 1, 39, 5, 39, 545, 8, 39, 10, 39, 12, 39, 548, 9, 39, 1, 40, 1, 40, 3, 40, 552, 8, 40, 1, 40, 1, 40, 3, 40, 556, 8, 40, 1, 40, 1, 40, 3, 40, 560, 8, 40, 1, 41, 1, 41, 1, 41, 1, 41, 3, 41, 566, 8, 41, 3, 41, 568, 8, 41, 1, 42, 1, 42, 1, 42, 5, 42, 573, 8, 42, 10, 42, 12, 42, 576, 9, 42, 1, 43, 1, 43, 1, 43, 1, 43, 1, 44, 3, 44, 583, 8, 44, 1, 44, 3, 44, 586, 8, 44, 1, 44, 3, 44, 589, 8, 44, 1, 45, 1, 45, 1, 45, 1, 45, 1, 46, 1, 46, 1, 46, 1, 46, 1, 47, 1, 47, 1, 47, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 3, 48, 608, 8, 48, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 3, 49, 622, 8, 49, 1, 50, 1, 50, 1, 50, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 5, 51, 636, 8, 51, 10, 51, 12, 51, 639, 9, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 5, 51, 648, 8, 51, 10, 51, 12, 51, 651, 9, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 5, 51, 660, 8, 51, 10, 51, 12, 51, 663, 9, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 3, 51, 670, 8, 51, 1, 51, 1, 51, 3, 51, 674, 8, 51, 1, 52, 1, 52, 1, 52, 5, 52, 679, 8, 52, 10, 52, 12, 52, 682, 9, 52, 1, 53, 1, 53, 1, 53, 3, 53, 687, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 4, 53, 694, 8, 53, 11, 53, 12, 53, 695, 1, 53, 1, 53, 3, 53, 700, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 724, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 741, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 747, 8, 53, 1, 53, 3, 53, 750, 8, 53, 1, 53, 3, 53, 753, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 763, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 769, 8, 53, 1, 53, 3, 53, 772, 8, 53, 1, 53, 3, 53, 775, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 783, 8, 53, 1, 53, 3, 53, 786, 8, 53, 1, 53, 1, 53, 3, 53, 790, 8, 53, 1, 53, 3, 53, 793, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 807, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 824, 8, 53, 1, 53, 1, 53, 1, 53, 3, 53, 829, 8, 53, 1, 53, 1, 53, 3, 53, 833, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 839, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 846, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 858, 8, 53, 1, 53, 1, 53, 3, 53, 862, 8, 53, 1, 53, 3, 53, 865, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 874, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 888, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 915, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 924, 8, 53, 5, 53, 926, 8, 53, 10, 53, 12, 53, 929, 9, 53, 1, 54, 1, 54, 1, 54, 5, 54, 934, 8, 54, 10, 54, 12, 54, 937, 9, 54, 1, 55, 1, 55, 3, 55, 941, 8, 55, 1, 56, 1, 56, 1, 56, 1, 56, 5, 56, 947, 8, 56, 10, 56, 12, 56, 950, 9, 56, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 5, 56, 957, 8, 56, 10, 56, 12, 56, 960, 9, 56, 3, 56, 962, 8, 56, 1, 56, 1, 56, 1, 56, 1, 57, 1, 57, 1, 57, 5, 57, 970, 8, 57, 10, 57, 12, 57, 973, 9, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 5, 57, 981, 8, 57, 10, 57, 12, 57, 984, 9, 57, 1, 57, 1, 57, 3, 57, 988, 8, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 3, 57, 995, 8, 57, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 3, 58, 1008, 8, 58, 1, 59, 1, 59, 1, 59, 5, 59, 1013, 8, 59, 10, 59, 12, 59, 1016, 9, 59, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 3, 60, 1028, 8, 60, 1, 61, 1, 61, 1, 61, 1, 61, 3, 61, 1034, 8, 61, 1, 61, 3, 61, 1037, 8, 61, 1, 62, 1, 62, 1, 62, 5, 62, 1042, 8, 62, 10, 62, 12, 62, 1045, 9, 62, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 3, 63, 1056, 8, 63, 1, 63, 1, 63, 1, 63, 1, 63, 3, 63, 1062, 8, 63, 5, 63, 1064, 8, 63, 10, 63, 12, 63, 1067, 9, 63, 1, 64, 1, 64, 1, 64, 3, 64, 1072, 8, 64, 1, 64, 1, 64, 1, 65, 1, 65, 1, 65, 3, 65, 1079, 8, 65, 1, 65, 1, 65, 1, 66, 1, 66, 1, 66, 5, 66, 1086, 8, 66, 10, 66, 12, 66, 1089, 9, 66, 1, 67, 1, 67, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 3, 68, 1099, 8, 68, 3, 68, 1101, 8, 68, 1, 69, 3, 69, 1104, 8, 69, 1, 69, 1, 69, 1, 69, 1, 69, 1, 69, 1, 69, 3, 69, 1112, 8, 69, 1, 70, 1, 70, 1, 70, 3, 70, 1117, 8, 70, 1, 71, 1, 71, 1, 72, 1, 72, 1, 73, 1, 73, 1, 74, 1, 74, 3, 74, 1127, 8, 74, 1, 75, 1, 75, 1, 75, 3, 75, 1132, 8, 75, 1, 76, 1, 76, 1, 76, 1, 76, 1, 77, 1, 77, 1, 77, 1, 77, 1, 78, 1, 78, 3, 78, 1144, 8, 78, 1, 79, 1, 79, 5, 79, 1148, 8, 79, 10, 79, 12, 79, 1151, 9, 79, 1, 79, 1, 79, 1, 80, 1, 80, 1, 80, 1, 80, 1, 80, 3, 80, 1160, 8, 80, 1, 81, 1, 81, 5, 81, 1164, 8, 81, 10, 81, 12, 81, 1167, 9, 81, 1, 81, 1, 81, 1, 82, 1, 82, 1, 82, 1, 82, 1, 82, 3, 82, 1176, 8, 82, 1, 82, 0, 3, 68, 106, 126, 83, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 0, 16, 2, 0, 17, 17, 72, 72, 2, 0, 42, 42, 49, 49, 3, 0, 1, 1, 4, 4, 8, 8, 4, 0, 1, 1, 3, 4, 8, 8, 78, 78, 2, 0, 49, 49, 71, 71, 2, 0, 1, 1, 4, 4, 2, 0, 7, 7, 21, 22, 2, 0, 28, 28, 47, 47, 2, 0, 69, 69, 74, 74, 3, 0, 10, 10, 48, 48, 87, 87, 2, 0, 39, 39, 51, 51, 1, 0, 103, 104, 2, 0, 114, 114, 134, 134, 7, 0, 20, 20, 36, 36, 53, 54, 68, 68, 76, 76, 93, 93, 99, 99, 12, 0, 1, 19, 21, 28, 30, 35, 37, 40, 42, 49, 51, 52, 56, 56, 58, 67, 69, 75, 77, 92, 94, 95, 97, 98, 4, 0, 19, 19, 28, 28, 37, 37, 46, 46, 1314, 0, 169, 1, 0, 0, 0, 2, 176, 1, 0, 0, 0, 4, 178, 1, 0, 0, 0, 6, 180, 1, 0, 0, 0, 8, 189, 1, 0, 0, 0, 10, 195, 1, 0, 0, 0, 12, 212, 1, 0, 0, 0, 14, 214, 1, 0, 0, 0, 16, 217, 1, 0, 0, 0, 18, 226, 1, 0, 0, 0, 20, 232, 1, 0, 0, 0, 22, 236, 1, 0, 0, 0, 24, 245, 1, 0, 0, 0, 26, 247, 1, 0, 0, 0, 28, 256, 1, 0, 0, 0, 30, 260, 1, 0, 0, 0, 32, 271, 1, 0, 0, 0, 34, 275, 1, 0, 0, 0, 36, 290, 1, 0, 0, 0, 38, 293, 1, 0, 0, 0, 40, 342, 1, 0, 0, 0, 42, 345, 1, 0, 0, 0, 44, 351, 1, 0, 0, 0, 46, 355, 1, 0, 0, 0, 48, 361, 1, 0, 0, 0, 50, 379, 1, 0, 0, 0, 52, 382, 1, 0, 0, 0, 54, 385, 1, 0, 0, 0, 56, 395, 1, 0, 0, 0, 58, 398, 1, 0, 0, 0, 60, 402, 1, 0, 0, 0, 62, 435, 1, 0, 0, 0, 64, 437, 1, 0, 0, 0, 66, 440, 1, 0, 0, 0, 68, 455, 1, 0, 0, 0, 70, 517, 1, 0, 0, 0, 72, 522, 1, 0, 0, 0, 74, 533, 1, 0, 0, 0, 76, 535, 1, 0, 0, 0, 78, 541, 1, 0, 0, 0, 80, 549, 1, 0, 0, 0, 82, 567, 1, 0, 0, 0, 84, 569, 1, 0, 0, 0, 86, 577, 1, 0, 0, 0, 88, 582, 1, 0, 0, 0, 90, 590, 1, 0, 0, 0, 92, 594, 1, 0, 0, 0, 94, 598, 1, 0, 0, 0, 96, 607, 1, 0, 0, 0, 98, 621, 1, 0, 0, 0, 100, 623, 1, 0, 0, 0, 102, 673, 1, 0, 0, 0, 104, 675, 1, 0, 0, 0, 106, 832, 1, 0, 0, 0, 108, 930, 1, 0, 0, 0, 110, 940, 1, 0, 0, 0, 112, 961, 1, 0, 0, 0, 114, 994, 1, 0, 0, 0, 116, 1007, 1, 0, 0, 0, 118, 1009, 1, 0, 0, 0, 120, 1027, 1, 0, 0, 0, 122, 1036, 1, 0, 0, 0, 124, 1038, 1, 0, 0, 0, 126, 1055, 1, 0, 0, 0, 128, 1068, 1, 0, 0, 0, 130, 1078, 1, 0, 0, 0, 132, 1082, 1, 0, 0, 0, 134, 1090, 1, 0, 0, 0, 136, 1100, 1, 0, 0, 0, 138, 1103, 1, 0, 0, 0, 140, 1116, 1, 0, 0, 0, 142, 1118, 1, 0, 0, 0, 144, 1120, 1, 0, 0, 0, 146, 1122, 1, 0, 0, 0, 148, 1126, 1, 0, 0, 0, 150, 1131, 1, 0, 0, 0, 152, 1133, 1, 0, 0, 0, 154, 1137, 1, 0, 0, 0, 156, 1143, 1, 0, 0, 0, 158, 1145, 1, 0, 0, 0, 160, 1159, 1, 0, 0, 0, 162, 1161, 1, 0, 0, 0, 164, 1175, 1, 0, 0, 0, 166, 168, 3, 2, 1, 0, 167, 166, 1, 0, 0, 0, 168, 171, 1, 0, 0, 0, 169, 167, 1, 0, 0, 0, 169, 170, 1, 0, 0, 0, 170, 172, 1, 0, 0, 0, 171, 169, 1, 0, 0, 0, 172, 173, 5, 0, 0, 1, 173, 1, 1, 0, 0, 0, 174, 177, 3, 6, 3, 0, 175, 177, 3, 12, 6, 0, 176, 174, 1, 0, 0, 0, 176, 175, 1, 0, 0, 0, 177, 3, 1, 0, 0, 0, 178, 179, 3, 106, 53, 0, 179, 5, 1, 0, 0, 0, 180, 181, 5, 50, 0, 0, 181, 185, 3, 150, 75, 0, 182, 183, 5, 111, 0, 0, 183, 184, 5, 118, 0, 0, 184, 186, 3, 4, 2, 0, 185, 182, 1, 0, 0, 0, 185, 186, 1, 0, 0, 0, 186, 187, 1, 0, 0, 0, 187, 188, 5, 145, 0, 0, 188, 7, 1, 0, 0, 0, 189, 190, 3, 4, 2, 0, 190, 191, 5, 111, 0, 0, 191, 192, 5, 118, 0, 0, 192, 193, 3, 4, 2, 0, 193, 194, 5, 145, 0, 0, 194, 9, 1, 0, 0, 0, 195, 200, 3, 150, 75, 0, 196, 197, 5, 112, 0, 0, 197, 199, 3, 150, 75, 0, 198, 196, 1, 0, 0, 0, 199, 202, 1, 0, 0, 0, 200, 198, 1, 0, 0, 0, 200, 201, 1, 0, 0, 0, 201, 11, 1, 0, 0, 0, 202, 200, 1, 0, 0, 0, 203, 213, 3, 20, 10, 0, 204, 213, 3, 24, 12, 0, 205, 213, 3, 14, 7, 0, 206, 213, 3, 16, 8, 0, 207, 213, 3, 18, 9, 0, 208, 213, 3, 22, 11, 0, 209, 213, 3, 8, 4, 0, 210, 213, 3, 20, 10, 0, 211, 213, 3, 26, 13, 0, 212, 203, 1, 0, 0, 0, 212, 204, 1, 0, 0, 0, 212, 205, 1, 0, 0, 0, 212, 206, 1, 0, 0, 0, 212, 207, 1, 0, 0, 0, 212, 208, 1, 0, 0, 0, 212, 209, 1, 0, 0, 0, 212, 210, 1, 0, 0, 0, 212, 211, 1, 0, 0, 0, 213, 13, 1, 0, 0, 0, 214, 215, 3, 4, 2, 0, 215, 216, 5, 145, 0, 0, 216, 15, 1, 0, 0, 0, 217, 218, 5, 38, 0, 0, 218, 219, 5, 126, 0, 0, 219, 220, 3, 4, 2, 0, 220, 221, 5, 144, 0, 0, 221, 224, 3, 12, 6, 0, 222, 223, 5, 24, 0, 0, 223, 225, 3, 12, 6, 0, 224, 222, 1, 0, 0, 0, 224, 225, 1, 0, 0, 0, 225, 17, 1, 0, 0, 0, 226, 227, 5, 96, 0, 0, 227, 228, 5, 126, 0, 0, 228, 229, 3, 4, 2, 0, 229, 230, 5, 144, 0, 0, 230, 231, 3, 12, 6, 0, 231, 19, 1, 0, 0, 0, 232, 233, 5, 70, 0, 0, 233, 234, 3, 4, 2, 0, 234, 235, 5, 145, 0, 0, 235, 21, 1, 0, 0, 0, 236, 237, 5, 29, 0, 0, 237, 238, 3, 150, 75, 0, 238, 240, 5, 126, 0, 0, 239, 241, 3, 10, 5, 0, 240, 239, 1, 0, 0, 0, 240, 241, 1, 0, 0, 0, 241, 242, 1, 0, 0, 0, 242, 243, 5, 144, 0, 0, 243, 244, 3, 26, 13, 0, 244, 23, 1, 0, 0, 0, 245, 246, 5, 145, 0, 0, 246, 25, 1, 0, 0, 0, 247, 251, 5, 124, 0, 0, 248, 250, 3, 2, 1, 0, 249, 248, 1, 0, 0, 0, 250, 253, 1, 0, 0, 0, 251, 249, 1, 0, 0, 0, 251, 252, 1, 0, 0, 0, 252, 254, 1, 0, 0, 0, 253, 251, 1, 0, 0, 0, 254, 255, 5, 142, 0, 0, 255, 27, 1, 0, 0, 0, 256, 257, 3, 4, 2, 0, 257, 258, 5, 111, 0, 0, 258, 259, 3, 4, 2, 0, 259, 29, 1, 0, 0, 0, 260, 265, 3, 28, 14, 0, 261, 262, 5, 112, 0, 0, 262, 264, 3, 28, 14, 0, 263, 261, 1, 0, 0, 0, 264, 267, 1, 0, 0, 0, 265, 263, 1, 0, 0, 0, 265, 266, 1, 0, 0, 0, 266, 31, 1, 0, 0, 0, 267, 265, 1, 0, 0, 0, 268, 272, 3, 34, 17, 0, 269, 272, 3, 38, 19, 0, 270, 272, 3, 114, 57, 0, 271, 268, 1, 0, 0, 0, 271, 269, 1, 0, 0, 0, 271, 270, 1, 0, 0, 0, 272, 273, 1, 0, 0, 0, 273, 274, 5, 0, 0, 1, 274, 33, 1, 0, 0, 0, 275, 281, 3, 36, 18, 0, 276, 277, 5, 91, 0, 0, 277, 278, 5, 1, 0, 0, 278, 280, 3, 36, 18, 0, 279, 276, 1, 0, 0, 0, 280, 283, 1, 0, 0, 0, 281, 279, 1, 0, 0, 0, 281, 282, 1, 0, 0, 0, 282, 35, 1, 0, 0, 0, 283, 281, 1, 0, 0, 0, 284, 291, 3, 38, 19, 0, 285, 286, 5, 126, 0, 0, 286, 287, 3, 34, 17, 0, 287, 288, 5, 144, 0, 0, 288, 291, 1, 0, 0, 0, 289, 291, 3, 154, 77, 0, 290, 284, 1, 0, 0, 0, 290, 285, 1, 0, 0, 0, 290, 289, 1, 0, 0, 0, 291, 37, 1, 0, 0, 0, 292, 294, 3, 40, 20, 0, 293, 292, 1, 0, 0, 0, 293, 294, 1, 0, 0, 0, 294, 295, 1, 0, 0, 0, 295, 297, 5, 77, 0, 0, 296, 298, 5, 23, 0, 0, 297, 296, 1, 0, 0, 0, 297, 298, 1, 0, 0, 0, 298, 300, 1, 0, 0, 0, 299, 301, 3, 42, 21, 0, 300, 299, 1, 0, 0, 0, 300, 301, 1, 0, 0, 0, 301, 302, 1, 0, 0, 0, 302, 304, 3, 104, 52, 0, 303, 305, 3, 44, 22, 0, 304, 303, 1, 0, 0, 0, 304, 305, 1, 0, 0, 0, 305, 307, 1, 0, 0, 0, 306, 308, 3, 46, 23, 0, 307, 306, 1, 0, 0, 0, 307, 308, 1, 0, 0, 0, 308, 310, 1, 0, 0, 0, 309, 311, 3, 50, 25, 0, 310, 309, 1, 0, 0, 0, 310, 311, 1, 0, 0, 0, 311, 313, 1, 0, 0, 0, 312, 314, 3, 52, 26, 0, 313, 312, 1, 0, 0, 0, 313, 314, 1, 0, 0, 0, 314, 316, 1, 0, 0, 0, 315, 317, 3, 54, 27, 0, 316, 315, 1, 0, 0, 0, 316, 317, 1, 0, 0, 0, 317, 320, 1, 0, 0, 0, 318, 319, 5, 98, 0, 0, 319, 321, 7, 0, 0, 0, 320, 318, 1, 0, 0, 0, 320, 321, 1, 0, 0, 0, 321, 324, 1, 0, 0, 0, 322, 323, 5, 98, 0, 0, 323, 325, 5, 86, 0, 0, 324, 322, 1, 0, 0, 0, 324, 325, 1, 0, 0, 0, 325, 327, 1, 0, 0, 0, 326, 328, 3, 56, 28, 0, 327, 326, 1, 0, 0, 0, 327, 328, 1, 0, 0, 0, 328, 330, 1, 0, 0, 0, 329, 331, 3, 48, 24, 0, 330, 329, 1, 0, 0, 0, 330, 331, 1, 0, 0, 0, 331, 333, 1, 0, 0, 0, 332, 334, 3, 58, 29, 0, 333, 332, 1, 0, 0, 0, 333, 334, 1, 0, 0, 0, 334, 337, 1, 0, 0, 0, 335, 338, 3, 62, 31, 0, 336, 338, 3, 64, 32, 0, 337, 335, 1, 0, 0, 0, 337, 336, 1, 0, 0, 0, 337, 338, 1, 0, 0, 0, 338, 340, 1, 0, 0, 0, 339, 341, 3, 66, 33, 0, 340, 339, 1, 0, 0, 0, 340, 341, 1, 0, 0, 0, 341, 39, 1, 0, 0, 0, 342, 343, 5, 98, 0, 0, 343, 344, 3, 118, 59, 0, 344, 41, 1, 0, 0, 0, 345, 346, 5, 85, 0, 0, 346, 349, 5, 104, 0, 0, 347, 348, 5, 98, 0, 0, 348, 350, 5, 82, 0, 0, 349, 347, 1, 0, 0, 0, 349, 350, 1, 0, 0, 0, 350, 43, 1, 0, 0, 0, 351, 352, 5, 32, 0, 0, 352, 353, 3, 68, 34, 0, 353, 45, 1, 0, 0, 0, 354, 356, 7, 1, 0, 0, 355, 354, 1, 0, 0, 0, 355, 356, 1, 0, 0, 0, 356, 357, 1, 0, 0, 0, 357, 358, 5, 5, 0, 0, 358, 359, 5, 45, 0, 0, 359, 360, 3, 104, 52, 0, 360, 47, 1, 0, 0, 0, 361, 362, 5, 97, 0, 0, 362, 363, 3, 150, 75, 0, 363, 364, 5, 6, 0, 0, 364, 365, 5, 126, 0, 0, 365, 366, 3, 88, 44, 0, 366, 376, 5, 144, 0, 0, 367, 368, 5, 112, 0, 0, 368, 369, 3, 150, 75, 0, 369, 370, 5, 6, 0, 0, 370, 371, 5, 126, 0, 0, 371, 372, 3, 88, 44, 0, 372, 373, 5, 144, 0, 0, 373, 375, 1, 0, 0, 0, 374, 367, 1, 0, 0, 0, 375, 378, 1, 0, 0, 0, 376, 374, 1, 0, 0, 0, 376, 377, 1, 0, 0, 0, 377, 49, 1, 0, 0, 0, 378, 376, 1, 0, 0, 0, 379, 380, 5, 67, 0, 0, 380, 381, 3, 106, 53, 0, 381, 51, 1, 0, 0, 0, 382, 383, 5, 95, 0, 0, 383, 384, 3, 106, 53, 0, 384, 53, 1, 0, 0, 0, 385, 386, 5, 34, 0, 0, 386, 393, 5, 11, 0, 0, 387, 388, 7, 0, 0, 0, 388, 389, 5, 126, 0, 0, 389, 390, 3, 104, 52, 0, 390, 391, 5, 144, 0, 0, 391, 394, 1, 0, 0, 0, 392, 394, 3, 104, 52, 0, 393, 387, 1, 0, 0, 0, 393, 392, 1, 0, 0, 0, 394, 55, 1, 0, 0, 0, 395, 396, 5, 35, 0, 0, 396, 397, 3, 106, 53, 0, 397, 57, 1, 0, 0, 0, 398, 399, 5, 62, 0, 0, 399, 400, 5, 11, 0, 0, 400, 401, 3, 78, 39, 0, 401, 59, 1, 0, 0, 0, 402, 403, 5, 62, 0, 0, 403, 404, 5, 11, 0, 0, 404, 405, 3, 104, 52, 0, 405, 61, 1, 0, 0, 0, 406, 407, 5, 52, 0, 0, 407, 410, 3, 106, 53, 0, 408, 409, 5, 112, 0, 0, 409, 411, 3, 106, 53, 0, 410, 408, 1, 0, 0, 0, 410, 411, 1, 0, 0, 0, 411, 416, 1, 0, 0, 0, 412, 413, 5, 98, 0, 0, 413, 417, 5, 82, 0, 0, 414, 415, 5, 11, 0, 0, 415, 417, 3, 104, 52, 0, 416, 412, 1, 0, 0, 0, 416, 414, 1, 0, 0, 0, 416, 417, 1, 0, 0, 0, 417, 436, 1, 0, 0, 0, 418, 419, 5, 52, 0, 0, 419, 422, 3, 106, 53, 0, 420, 421, 5, 98, 0, 0, 421, 423, 5, 82, 0, 0, 422, 420, 1, 0, 0, 0, 422, 423, 1, 0, 0, 0, 423, 424, 1, 0, 0, 0, 424, 425, 5, 59, 0, 0, 425, 426, 3, 106, 53, 0, 426, 436, 1, 0, 0, 0, 427, 428, 5, 52, 0, 0, 428, 429, 3, 106, 53, 0, 429, 430, 5, 59, 0, 0, 430, 433, 3, 106, 53, 0, 431, 432, 5, 11, 0, 0, 432, 434, 3, 104, 52, 0, 433, 431, 1, 0, 0, 0, 433, 434, 1, 0, 0, 0, 434, 436, 1, 0, 0, 0, 435, 406, 1, 0, 0, 0, 435, 418, 1, 0, 0, 0, 435, 427, 1, 0, 0, 0, 436, 63, 1, 0, 0, 0, 437, 438, 5, 59, 0, 0, 438, 439, 3, 106, 53, 0, 439, 65, 1, 0, 0, 0, 440, 441, 5, 79, 0, 0, 441, 442, 3, 84, 42, 0, 442, 67, 1, 0, 0, 0, 443, 444, 6, 34, -1, 0, 444, 446, 3, 126, 63, 0, 445, 447, 5, 27, 0, 0, 446, 445, 1, 0, 0, 0, 446, 447, 1, 0, 0, 0, 447, 449, 1, 0, 0, 0, 448, 450, 3, 76, 38, 0, 449, 448, 1, 0, 0, 0, 449, 450, 1, 0, 0, 0, 450, 456, 1, 0, 0, 0, 451, 452, 5, 126, 0, 0, 452, 453, 3, 68, 34, 0, 453, 454, 5, 144, 0, 0, 454, 456, 1, 0, 0, 0, 455, 443, 1, 0, 0, 0, 455, 451, 1, 0, 0, 0, 456, 471, 1, 0, 0, 0, 457, 458, 10, 3, 0, 0, 458, 459, 3, 72, 36, 0, 459, 460, 3, 68, 34, 4, 460, 470, 1, 0, 0, 0, 461, 463, 10, 4, 0, 0, 462, 464, 3, 70, 35, 0, 463, 462, 1, 0, 0, 0, 463, 464, 1, 0, 0, 0, 464, 465, 1, 0, 0, 0, 465, 466, 5, 45, 0, 0, 466, 467, 3, 68, 34, 0, 467, 468, 3, 74, 37, 0, 468, 470, 1, 0, 0, 0, 469, 457, 1, 0, 0, 0, 469, 461, 1, 0, 0, 0, 470, 473, 1, 0, 0, 0, 471, 469, 1, 0, 0, 0, 471, 472, 1, 0, 0, 0, 472, 69, 1, 0, 0, 0, 473, 471, 1, 0, 0, 0, 474, 476, 7, 2, 0, 0, 475, 474, 1, 0, 0, 0, 475, 476, 1, 0, 0, 0, 476, 477, 1, 0, 0, 0, 477, 484, 5, 42, 0, 0, 478, 480, 5, 42, 0, 0, 479, 481, 7, 2, 0, 0, 480, 479, 1, 0, 0, 0, 480, 481, 1, 0, 0, 0, 481, 484, 1, 0, 0, 0, 482, 484, 7, 2, 0, 0, 483, 475, 1, 0, 0, 0, 483, 478, 1, 0, 0, 0, 483, 482, 1, 0, 0, 0, 484, 518, 1, 0, 0, 0, 485, 487, 7, 3, 0, 0, 486, 485, 1, 0, 0, 0, 486, 487, 1, 0, 0, 0, 487, 488, 1, 0, 0, 0, 488, 490, 7, 4, 0, 0, 489, 491, 5, 63, 0, 0, 490, 489, 1, 0, 0, 0, 490, 491, 1, 0, 0, 0, 491, 500, 1, 0, 0, 0, 492, 494, 7, 4, 0, 0, 493, 495, 5, 63, 0, 0, 494, 493, 1, 0, 0, 0, 494, 495, 1, 0, 0, 0, 495, 497, 1, 0, 0, 0, 496, 498, 7, 3, 0, 0, 497, 496, 1, 0, 0, 0, 497, 498, 1, 0, 0, 0, 498, 500, 1, 0, 0, 0, 499, 486, 1, 0, 0, 0, 499, 492, 1, 0, 0, 0, 500, 518, 1, 0, 0, 0, 501, 503, 7, 5, 0, 0, 502, 501, 1, 0, 0, 0, 502, 503, 1, 0, 0, 0, 503, 504, 1, 0, 0, 0, 504, 506, 5, 33, 0, 0, 505, 507, 5, 63, 0, 0, 506, 505, 1, 0, 0, 0, 506, 507, 1, 0, 0, 0, 507, 516, 1, 0, 0, 0, 508, 510, 5, 33, 0, 0, 509, 511, 5, 63, 0, 0, 510, 509, 1, 0, 0, 0, 510, 511, 1, 0, 0, 0, 511, 513, 1, 0, 0, 0, 512, 514, 7, 5, 0, 0, 513, 512, 1, 0, 0, 0, 513, 514, 1, 0, 0, 0, 514, 516, 1, 0, 0, 0, 515, 502, 1, 0, 0, 0, 515, 508, 1, 0, 0, 0, 516, 518, 1, 0, 0, 0, 517, 483, 1, 0, 0, 0, 517, 499, 1, 0, 0, 0, 517, 515, 1, 0, 0, 0, 518, 71, 1, 0, 0, 0, 519, 520, 5, 16, 0, 0, 520, 523, 5, 45, 0, 0, 521, 523, 5, 112, 0, 0, 522, 519, 1, 0, 0, 0, 522, 521, 1, 0, 0, 0, 523, 73, 1, 0, 0, 0, 524, 525, 5, 60, 0, 0, 525, 534, 3, 104, 52, 0, 526, 527, 5, 92, 0, 0, 527, 528, 5, 126, 0, 0, 528, 529, 3, 104, 52, 0, 529, 530, 5, 144, 0, 0, 530, 534, 1, 0, 0, 0, 531, 532, 5, 92, 0, 0, 532, 534, 3, 104, 52, 0, 533, 524, 1, 0, 0, 0, 533, 526, 1, 0, 0, 0, 533, 531, 1, 0, 0, 0, 534, 75, 1, 0, 0, 0, 535, 536, 5, 75, 0, 0, 536, 539, 3, 82, 41, 0, 537, 538, 5, 59, 0, 0, 538, 540, 3, 82, 41, 0, 539, 537, 1, 0, 0, 0, 539, 540, 1, 0, 0, 0, 540, 77, 1, 0, 0, 0, 541, 546, 3, 80, 40, 0, 542, 543, 5, 112, 0, 0, 543, 545, 3, 80, 40, 0, 544, 542, 1, 0, 0, 0, 545, 548, 1, 0, 0, 0, 546, 544, 1, 0, 0, 0, 546, 547, 1, 0, 0, 0, 547, 79, 1, 0, 0, 0, 548, 546, 1, 0, 0, 0, 549, 551, 3, 106, 53, 0, 550, 552, 7, 6, 0, 0, 551, 550, 1, 0, 0, 0, 551, 552, 1, 0, 0, 0, 552, 555, 1, 0, 0, 0, 553, 554, 5, 58, 0, 0, 554, 556, 7, 7, 0, 0, 555, 553, 1, 0, 0, 0, 555, 556, 1, 0, 0, 0, 556, 559, 1, 0, 0, 0, 557, 558, 5, 15, 0, 0, 558, 560, 5, 106, 0, 0, 559, 557, 1, 0, 0, 0, 559, 560, 1, 0, 0, 0, 560, 81, 1, 0, 0, 0, 561, 568, 3, 154, 77, 0, 562, 565, 3, 138, 69, 0, 563, 564, 5, 146, 0, 0, 564, 566, 3, 138, 69, 0, 565, 563, 1, 0, 0, 0, 565, 566, 1, 0, 0, 0, 566, 568, 1, 0, 0, 0, 567, 561, 1, 0, 0, 0, 567, 562, 1, 0, 0, 0, 568, 83, 1, 0, 0, 0, 569, 574, 3, 86, 43, 0, 570, 571, 5, 112, 0, 0, 571, 573, 3, 86, 43, 0, 572, 570, 1, 0, 0, 0, 573, 576, 1, 0, 0, 0, 574, 572, 1, 0, 0, 0, 574, 575, 1, 0, 0, 0, 575, 85, 1, 0, 0, 0, 576, 574, 1, 0, 0, 0, 577, 578, 3, 150, 75, 0, 578, 579, 5, 118, 0, 0, 579, 580, 3, 140, 70, 0, 580, 87, 1, 0, 0, 0, 581, 583, 3, 90, 45, 0, 582, 581, 1, 0, 0, 0, 582, 583, 1, 0, 0, 0, 583, 585, 1, 0, 0, 0, 584, 586, 3, 92, 46, 0, 585, 584, 1, 0, 0, 0, 585, 586, 1, 0, 0, 0, 586, 588, 1, 0, 0, 0, 587, 589, 3, 94, 47, 0, 588, 587, 1, 0, 0, 0, 588, 589, 1, 0, 0, 0, 589, 89, 1, 0, 0, 0, 590, 591, 5, 65, 0, 0, 591, 592, 5, 11, 0, 0, 592, 593, 3, 104, 52, 0, 593, 91, 1, 0, 0, 0, 594, 595, 5, 62, 0, 0, 595, 596, 5, 11, 0, 0, 596, 597, 3, 78, 39, 0, 597, 93, 1, 0, 0, 0, 598, 599, 7, 8, 0, 0, 599, 600, 3, 96, 48, 0, 600, 95, 1, 0, 0, 0, 601, 608, 3, 98, 49, 0, 602, 603, 5, 9, 0, 0, 603, 604, 3, 98, 49, 0, 604, 605, 5, 2, 0, 0, 605, 606, 3, 98, 49, 0, 606, 608, 1, 0, 0, 0, 607, 601, 1, 0, 0, 0, 607, 602, 1, 0, 0, 0, 608, 97, 1, 0, 0, 0, 609, 610, 5, 18, 0, 0, 610, 622, 5, 73, 0, 0, 611, 612, 5, 90, 0, 0, 612, 622, 5, 66, 0, 0, 613, 614, 5, 90, 0, 0, 614, 622, 5, 30, 0, 0, 615, 616, 3, 138, 69, 0, 616, 617, 5, 66, 0, 0, 617, 622, 1, 0, 0, 0, 618, 619, 3, 138, 69, 0, 619, 620, 5, 30, 0, 0, 620, 622, 1, 0, 0, 0, 621, 609, 1, 0, 0, 0, 621, 611, 1, 0, 0, 0, 621, 613, 1, 0, 0, 0, 621, 615, 1, 0, 0, 0, 621, 618, 1, 0, 0, 0, 622, 99, 1, 0, 0, 0, 623, 624, 3, 106, 53, 0, 624, 625, 5, 0, 0, 1, 625, 101, 1, 0, 0, 0, 626, 674, 3, 150, 75, 0, 627, 628, 3, 150, 75, 0, 628, 629, 5, 126, 0, 0, 629, 630, 3, 150, 75, 0, 630, 637, 3, 102, 51, 0, 631, 632, 5, 112, 0, 0, 632, 633, 3, 150, 75, 0, 633, 634, 3, 102, 51, 0, 634, 636, 1, 0, 0, 0, 635, 631, 1, 0, 0, 0, 636, 639, 1, 0, 0, 0, 637, 635, 1, 0, 0, 0, 637, 638, 1, 0, 0, 0, 638, 640, 1, 0, 0, 0, 639, 637, 1, 0, 0, 0, 640, 641, 5, 144, 0, 0, 641, 674, 1, 0, 0, 0, 642, 643, 3, 150, 75, 0, 643, 644, 5, 126, 0, 0, 644, 649, 3, 152, 76, 0, 645, 646, 5, 112, 0, 0, 646, 648, 3, 152, 76, 0, 647, 645, 1, 0, 0, 0, 648, 651, 1, 0, 0, 0, 649, 647, 1, 0, 0, 0, 649, 650, 1, 0, 0, 0, 650, 652, 1, 0, 0, 0, 651, 649, 1, 0, 0, 0, 652, 653, 5, 144, 0, 0, 653, 674, 1, 0, 0, 0, 654, 655, 3, 150, 75, 0, 655, 656, 5, 126, 0, 0, 656, 661, 3, 102, 51, 0, 657, 658, 5, 112, 0, 0, 658, 660, 3, 102, 51, 0, 659, 657, 1, 0, 0, 0, 660, 663, 1, 0, 0, 0, 661, 659, 1, 0, 0, 0, 661, 662, 1, 0, 0, 0, 662, 664, 1, 0, 0, 0, 663, 661, 1, 0, 0, 0, 664, 665, 5, 144, 0, 0, 665, 674, 1, 0, 0, 0, 666, 667, 3, 150, 75, 0, 667, 669, 5, 126, 0, 0, 668, 670, 3, 104, 52, 0, 669, 668, 1, 0, 0, 0, 669, 670, 1, 0, 0, 0, 670, 671, 1, 0, 0, 0, 671, 672, 5, 144, 0, 0, 672, 674, 1, 0, 0, 0, 673, 626, 1, 0, 0, 0, 673, 627, 1, 0, 0, 0, 673, 642, 1, 0, 0, 0, 673, 654, 1, 0, 0, 0, 673, 666, 1, 0, 0, 0, 674, 103, 1, 0, 0, 0, 675, 680, 3, 106, 53, 0, 676, 677, 5, 112, 0, 0, 677, 679, 3, 106, 53, 0, 678, 676, 1, 0, 0, 0, 679, 682, 1, 0, 0, 0, 680, 678, 1, 0, 0, 0, 680, 681, 1, 0, 0, 0, 681, 105, 1, 0, 0, 0, 682, 680, 1, 0, 0, 0, 683, 684, 6, 53, -1, 0, 684, 686, 5, 12, 0, 0, 685, 687, 3, 106, 53, 0, 686, 685, 1, 0, 0, 0, 686, 687, 1, 0, 0, 0, 687, 693, 1, 0, 0, 0, 688, 689, 5, 94, 0, 0, 689, 690, 3, 106, 53, 0, 690, 691, 5, 81, 0, 0, 691, 692, 3, 106, 53, 0, 692, 694, 1, 0, 0, 0, 693, 688, 1, 0, 0, 0, 694, 695, 1, 0, 0, 0, 695, 693, 1, 0, 0, 0, 695, 696, 1, 0, 0, 0, 696, 699, 1, 0, 0, 0, 697, 698, 5, 24, 0, 0, 698, 700, 3, 106, 53, 0, 699, 697, 1, 0, 0, 0, 699, 700, 1, 0, 0, 0, 700, 701, 1, 0, 0, 0, 701, 702, 5, 25, 0, 0, 702, 833, 1, 0, 0, 0, 703, 704, 5, 13, 0, 0, 704, 705, 5, 126, 0, 0, 705, 706, 3, 106, 53, 0, 706, 707, 5, 6, 0, 0, 707, 708, 3, 102, 51, 0, 708, 709, 5, 144, 0, 0, 709, 833, 1, 0, 0, 0, 710, 711, 5, 19, 0, 0, 711, 833, 5, 106, 0, 0, 712, 713, 5, 43, 0, 0, 713, 714, 3, 106, 53, 0, 714, 715, 3, 142, 71, 0, 715, 833, 1, 0, 0, 0, 716, 717, 5, 80, 0, 0, 717, 718, 5, 126, 0, 0, 718, 719, 3, 106, 53, 0, 719, 720, 5, 32, 0, 0, 720, 723, 3, 106, 53, 0, 721, 722, 5, 31, 0, 0, 722, 724, 3, 106, 53, 0, 723, 721, 1, 0, 0, 0, 723, 724, 1, 0, 0, 0, 724, 725, 1, 0, 0, 0, 725, 726, 5, 144, 0, 0, 726, 833, 1, 0, 0, 0, 727, 728, 5, 83, 0, 0, 728, 833, 5, 106, 0, 0, 729, 730, 5, 88, 0, 0, 730, 731, 5, 126, 0, 0, 731, 732, 7, 9, 0, 0, 732, 733, 3, 156, 78, 0, 733, 734, 5, 32, 0, 0, 734, 735, 3, 106, 53, 0, 735, 736, 5, 144, 0, 0, 736, 833, 1, 0, 0, 0, 737, 738, 3, 150, 75, 0, 738, 740, 5, 126, 0, 0, 739, 741, 3, 104, 52, 0, 740, 739, 1, 0, 0, 0, 740, 741, 1, 0, 0, 0, 741, 742, 1, 0, 0, 0, 742, 743, 5, 144, 0, 0, 743, 752, 1, 0, 0, 0, 744, 746, 5, 126, 0, 0, 745, 747, 5, 23, 0, 0, 746, 745, 1, 0, 0, 0, 746, 747, 1, 0, 0, 0, 747, 749, 1, 0, 0, 0, 748, 750, 3, 108, 54, 0, 749, 748, 1, 0, 0, 0, 749, 750, 1, 0, 0, 0, 750, 751, 1, 0, 0, 0, 751, 753, 5, 144, 0, 0, 752, 744, 1, 0, 0, 0, 752, 753, 1, 0, 0, 0, 753, 754, 1, 0, 0, 0, 754, 755, 5, 64, 0, 0, 755, 756, 5, 126, 0, 0, 756, 757, 3, 88, 44, 0, 757, 758, 5, 144, 0, 0, 758, 833, 1, 0, 0, 0, 759, 760, 3, 150, 75, 0, 760, 762, 5, 126, 0, 0, 761, 763, 3, 104, 52, 0, 762, 761, 1, 0, 0, 0, 762, 763, 1, 0, 0, 0, 763, 764, 1, 0, 0, 0, 764, 765, 5, 144, 0, 0, 765, 774, 1, 0, 0, 0, 766, 768, 5, 126, 0, 0, 767, 769, 5, 23, 0, 0, 768, 767, 1, 0, 0, 0, 768, 769, 1, 0, 0, 0, 769, 771, 1, 0, 0, 0, 770, 772, 3, 108, 54, 0, 771, 770, 1, 0, 0, 0, 771, 772, 1, 0, 0, 0, 772, 773, 1, 0, 0, 0, 773, 775, 5, 144, 0, 0, 774, 766, 1, 0, 0, 0, 774, 775, 1, 0, 0, 0, 775, 776, 1, 0, 0, 0, 776, 777, 5, 64, 0, 0, 777, 778, 3, 150, 75, 0, 778, 833, 1, 0, 0, 0, 779, 785, 3, 150, 75, 0, 780, 782, 5, 126, 0, 0, 781, 783, 3, 104, 52, 0, 782, 781, 1, 0, 0, 0, 782, 783, 1, 0, 0, 0, 783, 784, 1, 0, 0, 0, 784, 786, 5, 144, 0, 0, 785, 780, 1, 0, 0, 0, 785, 786, 1, 0, 0, 0, 786, 787, 1, 0, 0, 0, 787, 789, 5, 126, 0, 0, 788, 790, 5, 23, 0, 0, 789, 788, 1, 0, 0, 0, 789, 790, 1, 0, 0, 0, 790, 792, 1, 0, 0, 0, 791, 793, 3, 108, 54, 0, 792, 791, 1, 0, 0, 0, 792, 793, 1, 0, 0, 0, 793, 794, 1, 0, 0, 0, 794, 795, 5, 144, 0, 0, 795, 833, 1, 0, 0, 0, 796, 833, 3, 114, 57, 0, 797, 833, 3, 158, 79, 0, 798, 833, 3, 140, 70, 0, 799, 800, 5, 114, 0, 0, 800, 833, 3, 106, 53, 19, 801, 802, 5, 56, 0, 0, 802, 833, 3, 106, 53, 13, 803, 804, 3, 130, 65, 0, 804, 805, 5, 116, 0, 0, 805, 807, 1, 0, 0, 0, 806, 803, 1, 0, 0, 0, 806, 807, 1, 0, 0, 0, 807, 808, 1, 0, 0, 0, 808, 833, 5, 108, 0, 0, 809, 810, 5, 126, 0, 0, 810, 811, 3, 34, 17, 0, 811, 812, 5, 144, 0, 0, 812, 833, 1, 0, 0, 0, 813, 814, 5, 126, 0, 0, 814, 815, 3, 106, 53, 0, 815, 816, 5, 144, 0, 0, 816, 833, 1, 0, 0, 0, 817, 818, 5, 126, 0, 0, 818, 819, 3, 104, 52, 0, 819, 820, 5, 144, 0, 0, 820, 833, 1, 0, 0, 0, 821, 823, 5, 125, 0, 0, 822, 824, 3, 104, 52, 0, 823, 822, 1, 0, 0, 0, 823, 824, 1, 0, 0, 0, 824, 825, 1, 0, 0, 0, 825, 833, 5, 143, 0, 0, 826, 828, 5, 124, 0, 0, 827, 829, 3, 30, 15, 0, 828, 827, 1, 0, 0, 0, 828, 829, 1, 0, 0, 0, 829, 830, 1, 0, 0, 0, 830, 833, 5, 142, 0, 0, 831, 833, 3, 122, 61, 0, 832, 683, 1, 0, 0, 0, 832, 703, 1, 0, 0, 0, 832, 710, 1, 0, 0, 0, 832, 712, 1, 0, 0, 0, 832, 716, 1, 0, 0, 0, 832, 727, 1, 0, 0, 0, 832, 729, 1, 0, 0, 0, 832, 737, 1, 0, 0, 0, 832, 759, 1, 0, 0, 0, 832, 779, 1, 0, 0, 0, 832, 796, 1, 0, 0, 0, 832, 797, 1, 0, 0, 0, 832, 798, 1, 0, 0, 0, 832, 799, 1, 0, 0, 0, 832, 801, 1, 0, 0, 0, 832, 806, 1, 0, 0, 0, 832, 809, 1, 0, 0, 0, 832, 813, 1, 0, 0, 0, 832, 817, 1, 0, 0, 0, 832, 821, 1, 0, 0, 0, 832, 826, 1, 0, 0, 0, 832, 831, 1, 0, 0, 0, 833, 927, 1, 0, 0, 0, 834, 838, 10, 18, 0, 0, 835, 839, 5, 108, 0, 0, 836, 839, 5, 146, 0, 0, 837, 839, 5, 133, 0, 0, 838, 835, 1, 0, 0, 0, 838, 836, 1, 0, 0, 0, 838, 837, 1, 0, 0, 0, 839, 840, 1, 0, 0, 0, 840, 926, 3, 106, 53, 19, 841, 845, 10, 17, 0, 0, 842, 846, 5, 134, 0, 0, 843, 846, 5, 114, 0, 0, 844, 846, 5, 113, 0, 0, 845, 842, 1, 0, 0, 0, 845, 843, 1, 0, 0, 0, 845, 844, 1, 0, 0, 0, 846, 847, 1, 0, 0, 0, 847, 926, 3, 106, 53, 18, 848, 873, 10, 16, 0, 0, 849, 874, 5, 117, 0, 0, 850, 874, 5, 118, 0, 0, 851, 874, 5, 129, 0, 0, 852, 874, 5, 127, 0, 0, 853, 874, 5, 128, 0, 0, 854, 874, 5, 119, 0, 0, 855, 874, 5, 120, 0, 0, 856, 858, 5, 56, 0, 0, 857, 856, 1, 0, 0, 0, 857, 858, 1, 0, 0, 0, 858, 859, 1, 0, 0, 0, 859, 861, 5, 40, 0, 0, 860, 862, 5, 14, 0, 0, 861, 860, 1, 0, 0, 0, 861, 862, 1, 0, 0, 0, 862, 874, 1, 0, 0, 0, 863, 865, 5, 56, 0, 0, 864, 863, 1, 0, 0, 0, 864, 865, 1, 0, 0, 0, 865, 866, 1, 0, 0, 0, 866, 874, 7, 10, 0, 0, 867, 874, 5, 140, 0, 0, 868, 874, 5, 141, 0, 0, 869, 874, 5, 131, 0, 0, 870, 874, 5, 122, 0, 0, 871, 874, 5, 123, 0, 0, 872, 874, 5, 130, 0, 0, 873, 849, 1, 0, 0, 0, 873, 850, 1, 0, 0, 0, 873, 851, 1, 0, 0, 0, 873, 852, 1, 0, 0, 0, 873, 853, 1, 0, 0, 0, 873, 854, 1, 0, 0, 0, 873, 855, 1, 0, 0, 0, 873, 857, 1, 0, 0, 0, 873, 864, 1, 0, 0, 0, 873, 867, 1, 0, 0, 0, 873, 868, 1, 0, 0, 0, 873, 869, 1, 0, 0, 0, 873, 870, 1, 0, 0, 0, 873, 871, 1, 0, 0, 0, 873, 872, 1, 0, 0, 0, 874, 875, 1, 0, 0, 0, 875, 926, 3, 106, 53, 17, 876, 877, 10, 14, 0, 0, 877, 878, 5, 132, 0, 0, 878, 926, 3, 106, 53, 15, 879, 880, 10, 12, 0, 0, 880, 881, 5, 2, 0, 0, 881, 926, 3, 106, 53, 13, 882, 883, 10, 11, 0, 0, 883, 884, 5, 61, 0, 0, 884, 926, 3, 106, 53, 12, 885, 887, 10, 10, 0, 0, 886, 888, 5, 56, 0, 0, 887, 886, 1, 0, 0, 0, 887, 888, 1, 0, 0, 0, 888, 889, 1, 0, 0, 0, 889, 890, 5, 9, 0, 0, 890, 891, 3, 106, 53, 0, 891, 892, 5, 2, 0, 0, 892, 893, 3, 106, 53, 11, 893, 926, 1, 0, 0, 0, 894, 895, 10, 9, 0, 0, 895, 896, 5, 135, 0, 0, 896, 897, 3, 106, 53, 0, 897, 898, 5, 111, 0, 0, 898, 899, 3, 106, 53, 9, 899, 926, 1, 0, 0, 0, 900, 901, 10, 22, 0, 0, 901, 902, 5, 125, 0, 0, 902, 903, 3, 106, 53, 0, 903, 904, 5, 143, 0, 0, 904, 926, 1, 0, 0, 0, 905, 906, 10, 21, 0, 0, 906, 907, 5, 116, 0, 0, 907, 926, 5, 104, 0, 0, 908, 909, 10, 20, 0, 0, 909, 910, 5, 116, 0, 0, 910, 926, 3, 150, 75, 0, 911, 912, 10, 15, 0, 0, 912, 914, 5, 44, 0, 0, 913, 915, 5, 56, 0, 0, 914, 913, 1, 0, 0, 0, 914, 915, 1, 0, 0, 0, 915, 916, 1, 0, 0, 0, 916, 926, 5, 57, 0, 0, 917, 923, 10, 8, 0, 0, 918, 924, 3, 148, 74, 0, 919, 920, 5, 6, 0, 0, 920, 924, 3, 150, 75, 0, 921, 922, 5, 6, 0, 0, 922, 924, 5, 106, 0, 0, 923, 918, 1, 0, 0, 0, 923, 919, 1, 0, 0, 0, 923, 921, 1, 0, 0, 0, 924, 926, 1, 0, 0, 0, 925, 834, 1, 0, 0, 0, 925, 841, 1, 0, 0, 0, 925, 848, 1, 0, 0, 0, 925, 876, 1, 0, 0, 0, 925, 879, 1, 0, 0, 0, 925, 882, 1, 0, 0, 0, 925, 885, 1, 0, 0, 0, 925, 894, 1, 0, 0, 0, 925, 900, 1, 0, 0, 0, 925, 905, 1, 0, 0, 0, 925, 908, 1, 0, 0, 0, 925, 911, 1, 0, 0, 0, 925, 917, 1, 0, 0, 0, 926, 929, 1, 0, 0, 0, 927, 925, 1, 0, 0, 0, 927, 928, 1, 0, 0, 0, 928, 107, 1, 0, 0, 0, 929, 927, 1, 0, 0, 0, 930, 935, 3, 110, 55, 0, 931, 932, 5, 112, 0, 0, 932, 934, 3, 110, 55, 0, 933, 931, 1, 0, 0, 0, 934, 937, 1, 0, 0, 0, 935, 933, 1, 0, 0, 0, 935, 936, 1, 0, 0, 0, 936, 109, 1, 0, 0, 0, 937, 935, 1, 0, 0, 0, 938, 941, 3, 112, 56, 0, 939, 941, 3, 106, 53, 0, 940, 938, 1, 0, 0, 0, 940, 939, 1, 0, 0, 0, 941, 111, 1, 0, 0, 0, 942, 943, 5, 126, 0, 0, 943, 948, 3, 150, 75, 0, 944, 945, 5, 112, 0, 0, 945, 947, 3, 150, 75, 0, 946, 944, 1, 0, 0, 0, 947, 950, 1, 0, 0, 0, 948, 946, 1, 0, 0, 0, 948, 949, 1, 0, 0, 0, 949, 951, 1, 0, 0, 0, 950, 948, 1, 0, 0, 0, 951, 952, 5, 144, 0, 0, 952, 962, 1, 0, 0, 0, 953, 958, 3, 150, 75, 0, 954, 955, 5, 112, 0, 0, 955, 957, 3, 150, 75, 0, 956, 954, 1, 0, 0, 0, 957, 960, 1, 0, 0, 0, 958, 956, 1, 0, 0, 0, 958, 959, 1, 0, 0, 0, 959, 962, 1, 0, 0, 0, 960, 958, 1, 0, 0, 0, 961, 942, 1, 0, 0, 0, 961, 953, 1, 0, 0, 0, 962, 963, 1, 0, 0, 0, 963, 964, 5, 107, 0, 0, 964, 965, 3, 106, 53, 0, 965, 113, 1, 0, 0, 0, 966, 967, 5, 128, 0, 0, 967, 971, 3, 150, 75, 0, 968, 970, 3, 116, 58, 0, 969, 968, 1, 0, 0, 0, 970, 973, 1, 0, 0, 0, 971, 969, 1, 0, 0, 0, 971, 972, 1, 0, 0, 0, 972, 974, 1, 0, 0, 0, 973, 971, 1, 0, 0, 0, 974, 975, 5, 146, 0, 0, 975, 976, 5, 120, 0, 0, 976, 995, 1, 0, 0, 0, 977, 978, 5, 128, 0, 0, 978, 982, 3, 150, 75, 0, 979, 981, 3, 116, 58, 0, 980, 979, 1, 0, 0, 0, 981, 984, 1, 0, 0, 0, 982, 980, 1, 0, 0, 0, 982, 983, 1, 0, 0, 0, 983, 985, 1, 0, 0, 0, 984, 982, 1, 0, 0, 0, 985, 987, 5, 120, 0, 0, 986, 988, 3, 114, 57, 0, 987, 986, 1, 0, 0, 0, 987, 988, 1, 0, 0, 0, 988, 989, 1, 0, 0, 0, 989, 990, 5, 128, 0, 0, 990, 991, 5, 146, 0, 0, 991, 992, 3, 150, 75, 0, 992, 993, 5, 120, 0, 0, 993, 995, 1, 0, 0, 0, 994, 966, 1, 0, 0, 0, 994, 977, 1, 0, 0, 0, 995, 115, 1, 0, 0, 0, 996, 997, 3, 150, 75, 0, 997, 998, 5, 118, 0, 0, 998, 999, 3, 156, 78, 0, 999, 1008, 1, 0, 0, 0, 1000, 1001, 3, 150, 75, 0, 1001, 1002, 5, 118, 0, 0, 1002, 1003, 5, 124, 0, 0, 1003, 1004, 3, 106, 53, 0, 1004, 1005, 5, 142, 0, 0, 1005, 1008, 1, 0, 0, 0, 1006, 1008, 3, 150, 75, 0, 1007, 996, 1, 0, 0, 0, 1007, 1000, 1, 0, 0, 0, 1007, 1006, 1, 0, 0, 0, 1008, 117, 1, 0, 0, 0, 1009, 1014, 3, 120, 60, 0, 1010, 1011, 5, 112, 0, 0, 1011, 1013, 3, 120, 60, 0, 1012, 1010, 1, 0, 0, 0, 1013, 1016, 1, 0, 0, 0, 1014, 1012, 1, 0, 0, 0, 1014, 1015, 1, 0, 0, 0, 1015, 119, 1, 0, 0, 0, 1016, 1014, 1, 0, 0, 0, 1017, 1018, 3, 150, 75, 0, 1018, 1019, 5, 6, 0, 0, 1019, 1020, 5, 126, 0, 0, 1020, 1021, 3, 34, 17, 0, 1021, 1022, 5, 144, 0, 0, 1022, 1028, 1, 0, 0, 0, 1023, 1024, 3, 106, 53, 0, 1024, 1025, 5, 6, 0, 0, 1025, 1026, 3, 150, 75, 0, 1026, 1028, 1, 0, 0, 0, 1027, 1017, 1, 0, 0, 0, 1027, 1023, 1, 0, 0, 0, 1028, 121, 1, 0, 0, 0, 1029, 1037, 3, 154, 77, 0, 1030, 1031, 3, 130, 65, 0, 1031, 1032, 5, 116, 0, 0, 1032, 1034, 1, 0, 0, 0, 1033, 1030, 1, 0, 0, 0, 1033, 1034, 1, 0, 0, 0, 1034, 1035, 1, 0, 0, 0, 1035, 1037, 3, 124, 62, 0, 1036, 1029, 1, 0, 0, 0, 1036, 1033, 1, 0, 0, 0, 1037, 123, 1, 0, 0, 0, 1038, 1043, 3, 150, 75, 0, 1039, 1040, 5, 116, 0, 0, 1040, 1042, 3, 150, 75, 0, 1041, 1039, 1, 0, 0, 0, 1042, 1045, 1, 0, 0, 0, 1043, 1041, 1, 0, 0, 0, 1043, 1044, 1, 0, 0, 0, 1044, 125, 1, 0, 0, 0, 1045, 1043, 1, 0, 0, 0, 1046, 1047, 6, 63, -1, 0, 1047, 1056, 3, 130, 65, 0, 1048, 1056, 3, 128, 64, 0, 1049, 1050, 5, 126, 0, 0, 1050, 1051, 3, 34, 17, 0, 1051, 1052, 5, 144, 0, 0, 1052, 1056, 1, 0, 0, 0, 1053, 1056, 3, 114, 57, 0, 1054, 1056, 3, 154, 77, 0, 1055, 1046, 1, 0, 0, 0, 1055, 1048, 1, 0, 0, 0, 1055, 1049, 1, 0, 0, 0, 1055, 1053, 1, 0, 0, 0, 1055, 1054, 1, 0, 0, 0, 1056, 1065, 1, 0, 0, 0, 1057, 1061, 10, 3, 0, 0, 1058, 1062, 3, 148, 74, 0, 1059, 1060, 5, 6, 0, 0, 1060, 1062, 3, 150, 75, 0, 1061, 1058, 1, 0, 0, 0, 1061, 1059, 1, 0, 0, 0, 1062, 1064, 1, 0, 0, 0, 1063, 1057, 1, 0, 0, 0, 1064, 1067, 1, 0, 0, 0, 1065, 1063, 1, 0, 0, 0, 1065, 1066, 1, 0, 0, 0, 1066, 127, 1, 0, 0, 0, 1067, 1065, 1, 0, 0, 0, 1068, 1069, 3, 150, 75, 0, 1069, 1071, 5, 126, 0, 0, 1070, 1072, 3, 132, 66, 0, 1071, 1070, 1, 0, 0, 0, 1071, 1072, 1, 0, 0, 0, 1072, 1073, 1, 0, 0, 0, 1073, 1074, 5, 144, 0, 0, 1074, 129, 1, 0, 0, 0, 1075, 1076, 3, 134, 67, 0, 1076, 1077, 5, 116, 0, 0, 1077, 1079, 1, 0, 0, 0, 1078, 1075, 1, 0, 0, 0, 1078, 1079, 1, 0, 0, 0, 1079, 1080, 1, 0, 0, 0, 1080, 1081, 3, 150, 75, 0, 1081, 131, 1, 0, 0, 0, 1082, 1087, 3, 106, 53, 0, 1083, 1084, 5, 112, 0, 0, 1084, 1086, 3, 106, 53, 0, 1085, 1083, 1, 0, 0, 0, 1086, 1089, 1, 0, 0, 0, 1087, 1085, 1, 0, 0, 0, 1087, 1088, 1, 0, 0, 0, 1088, 133, 1, 0, 0, 0, 1089, 1087, 1, 0, 0, 0, 1090, 1091, 3, 150, 75, 0, 1091, 135, 1, 0, 0, 0, 1092, 1101, 5, 102, 0, 0, 1093, 1094, 5, 116, 0, 0, 1094, 1101, 7, 11, 0, 0, 1095, 1096, 5, 104, 0, 0, 1096, 1098, 5, 116, 0, 0, 1097, 1099, 7, 11, 0, 0, 1098, 1097, 1, 0, 0, 0, 1098, 1099, 1, 0, 0, 0, 1099, 1101, 1, 0, 0, 0, 1100, 1092, 1, 0, 0, 0, 1100, 1093, 1, 0, 0, 0, 1100, 1095, 1, 0, 0, 0, 1101, 137, 1, 0, 0, 0, 1102, 1104, 7, 12, 0, 0, 1103, 1102, 1, 0, 0, 0, 1103, 1104, 1, 0, 0, 0, 1104, 1111, 1, 0, 0, 0, 1105, 1112, 3, 136, 68, 0, 1106, 1112, 5, 103, 0, 0, 1107, 1112, 5, 104, 0, 0, 1108, 1112, 5, 105, 0, 0, 1109, 1112, 5, 41, 0, 0, 1110, 1112, 5, 55, 0, 0, 1111, 1105, 1, 0, 0, 0, 1111, 1106, 1, 0, 0, 0, 1111, 1107, 1, 0, 0, 0, 1111, 1108, 1, 0, 0, 0, 1111, 1109, 1, 0, 0, 0, 1111, 1110, 1, 0, 0, 0, 1112, 139, 1, 0, 0, 0, 1113, 1117, 3, 138, 69, 0, 1114, 1117, 5, 106, 0, 0, 1115, 1117, 5, 57, 0, 0, 1116, 1113, 1, 0, 0, 0, 1116, 1114, 1, 0, 0, 0, 1116, 1115, 1, 0, 0, 0, 1117, 141, 1, 0, 0, 0, 1118, 1119, 7, 13, 0, 0, 1119, 143, 1, 0, 0, 0, 1120, 1121, 7, 14, 0, 0, 1121, 145, 1, 0, 0, 0, 1122, 1123, 7, 15, 0, 0, 1123, 147, 1, 0, 0, 0, 1124, 1127, 5, 101, 0, 0, 1125, 1127, 3, 146, 73, 0, 1126, 1124, 1, 0, 0, 0, 1126, 1125, 1, 0, 0, 0, 1127, 149, 1, 0, 0, 0, 1128, 1132, 5, 101, 0, 0, 1129, 1132, 3, 142, 71, 0, 1130, 1132, 3, 144, 72, 0, 1131, 1128, 1, 0, 0, 0, 1131, 1129, 1, 0, 0, 0, 1131, 1130, 1, 0, 0, 0, 1132, 151, 1, 0, 0, 0, 1133, 1134, 3, 156, 78, 0, 1134, 1135, 5, 118, 0, 0, 1135, 1136, 3, 138, 69, 0, 1136, 153, 1, 0, 0, 0, 1137, 1138, 5, 124, 0, 0, 1138, 1139, 3, 150, 75, 0, 1139, 1140, 5, 142, 0, 0, 1140, 155, 1, 0, 0, 0, 1141, 1144, 5, 106, 0, 0, 1142, 1144, 3, 158, 79, 0, 1143, 1141, 1, 0, 0, 0, 1143, 1142, 1, 0, 0, 0, 1144, 157, 1, 0, 0, 0, 1145, 1149, 5, 137, 0, 0, 1146, 1148, 3, 160, 80, 0, 1147, 1146, 1, 0, 0, 0, 1148, 1151, 1, 0, 0, 0, 1149, 1147, 1, 0, 0, 0, 1149, 1150, 1, 0, 0, 0, 1150, 1152, 1, 0, 0, 0, 1151, 1149, 1, 0, 0, 0, 1152, 1153, 5, 139, 0, 0, 1153, 159, 1, 0, 0, 0, 1154, 1155, 5, 152, 0, 0, 1155, 1156, 3, 106, 53, 0, 1156, 1157, 5, 142, 0, 0, 1157, 1160, 1, 0, 0, 0, 1158, 1160, 5, 151, 0, 0, 1159, 1154, 1, 0, 0, 0, 1159, 1158, 1, 0, 0, 0, 1160, 161, 1, 0, 0, 0, 1161, 1165, 5, 138, 0, 0, 1162, 1164, 3, 164, 82, 0, 1163, 1162, 1, 0, 0, 0, 1164, 1167, 1, 0, 0, 0, 1165, 1163, 1, 0, 0, 0, 1165, 1166, 1, 0, 0, 0, 1166, 1168, 1, 0, 0, 0, 1167, 1165, 1, 0, 0, 0, 1168, 1169, 5, 0, 0, 1, 1169, 163, 1, 0, 0, 0, 1170, 1171, 5, 154, 0, 0, 1171, 1172, 3, 106, 53, 0, 1172, 1173, 5, 142, 0, 0, 1173, 1176, 1, 0, 0, 0, 1174, 1176, 5, 153, 0, 0, 1175, 1170, 1, 0, 0, 0, 1175, 1174, 1, 0, 0, 0, 1176, 165, 1, 0, 0, 0, 141, 169, 176, 185, 200, 212, 224, 240, 251, 265, 271, 281, 290, 293, 297, 300, 304, 307, 310, 313, 316, 320, 324, 327, 330, 333, 337, 340, 349, 355, 376, 393, 410, 416, 422, 433, 435, 446, 449, 455, 463, 469, 471, 475, 480, 483, 486, 490, 494, 497, 499, 502, 506, 510, 513, 515, 517, 522, 533, 539, 546, 551, 555, 559, 565, 567, 574, 582, 585, 588, 607, 621, 637, 649, 661, 669, 673, 680, 686, 695, 699, 723, 740, 746, 749, 752, 762, 768, 771, 774, 782, 785, 789, 792, 806, 823, 828, 832, 838, 845, 857, 861, 864, 873, 887, 914, 923, 925, 927, 935, 940, 948, 958, 961, 971, 982, 987, 994, 1007, 1014, 1027, 1033, 1036, 1043, 1055, 1061, 1065, 1071, 1078, 1087, 1098, 1100, 1103, 1111, 1116, 1126, 1131, 1143, 1149, 1159, 1165, 1175] \ No newline at end of file diff --git a/hogql_parser/parser.cpp b/hogql_parser/parser.cpp index 06e5ffb2e9d37..274aa741ae24a 100644 --- a/hogql_parser/parser.cpp +++ b/hogql_parser/parser.cpp @@ -1622,27 +1622,42 @@ class HogQLParseTreeConverter : public HogQLParserBaseVisitor { auto column_expr_list_ctx = ctx->columnExprList(); string name = visitAsString(ctx->identifier(0)); string over_identifier = visitAsString(ctx->identifier(1)); - PyObject* args = visitAsPyObjectOrEmptyList(column_expr_list_ctx); + PyObject* exprs = visitAsPyObjectOrEmptyList(column_expr_list_ctx); + PyObject* args; + try { + args = visitAsPyObjectOrEmptyList(ctx->columnArgList()); + } catch (...) { + Py_DECREF(exprs); + throw; + } RETURN_NEW_AST_NODE( - "WindowFunction", "{s:s#,s:N,s:s#}", "name", name.data(), name.size(), "args", args, "over_identifier", - over_identifier.data(), over_identifier.size() + "WindowFunction", "{s:s#,s:N,s:N,s:s#}", "name", name.data(), name.size(), "exprs", exprs, "args", args, + "over_identifier", over_identifier.data(), over_identifier.size() ); } VISIT(ColumnExprWinFunction) { string identifier = visitAsString(ctx->identifier()); auto column_expr_list_ctx = ctx->columnExprList(); - PyObject* args = visitAsPyObjectOrEmptyList(column_expr_list_ctx); + PyObject* exprs = visitAsPyObjectOrEmptyList(column_expr_list_ctx); + PyObject* args; + try { + args = visitAsPyObjectOrEmptyList(ctx->columnArgList()); + } catch (...) { + Py_DECREF(exprs); + throw; + } PyObject* over_expr; try { over_expr = visitAsPyObjectOrNone(ctx->windowExpr()); } catch (...) { + Py_DECREF(exprs); Py_DECREF(args); throw; } RETURN_NEW_AST_NODE( - "WindowFunction", "{s:s#,s:N,s:N}", "name", identifier.data(), identifier.size(), "args", args, "over_expr", - over_expr + "WindowFunction", "{s:s#,s:N,s:N,s:N}", "name", identifier.data(), identifier.size(), "exprs", exprs, + "args", args, "over_expr", over_expr ); } diff --git a/hogql_parser/setup.py b/hogql_parser/setup.py index 030b98ddb58be..ae4aff4cf8581 100644 --- a/hogql_parser/setup.py +++ b/hogql_parser/setup.py @@ -32,7 +32,7 @@ setup( name="hogql_parser", - version="1.0.11", + version="1.0.12", url="https://github.com/PostHog/posthog/tree/master/hogql_parser", author="PostHog Inc.", author_email="hey@posthog.com", diff --git a/hogvm/typescript/package.json b/hogvm/typescript/package.json index b7977c949edad..3080a86f544f9 100644 --- a/hogvm/typescript/package.json +++ b/hogvm/typescript/package.json @@ -1,9 +1,9 @@ { "name": "@posthog/hogvm", - "version": "1.0.10", - "description": "PostHog HogQL Virtual Machine", - "types": "dist/execute.d.ts", - "main": "dist/execute.js", + "version": "1.0.11", + "description": "PostHog Hog Virtual Machine", + "types": "dist/index.d.ts", + "main": "dist/index.js", "packageManager": "pnpm@8.3.1", "scripts": { "test": "jest --runInBand --forceExit", diff --git a/hogvm/typescript/src/execute.ts b/hogvm/typescript/src/execute.ts index 0bb1c0b5c81d5..4101d64f69d1b 100644 --- a/hogvm/typescript/src/execute.ts +++ b/hogvm/typescript/src/execute.ts @@ -1,6 +1,6 @@ import { Operation } from './operation' import { ASYNC_STL, STL } from './stl/stl' -import { convertJSToHog, getNestedValue, like, setNestedValue } from './utils' +import { convertHogToJS, convertJSToHog, getNestedValue, like, setNestedValue } from './utils' const DEFAULT_MAX_ASYNC_STEPS = 100 const DEFAULT_TIMEOUT = 5 // seconds @@ -58,8 +58,10 @@ export async function execAsync(bytecode: any[], options?: ExecOptions): Promise if (response.state && response.asyncFunctionName && response.asyncFunctionArgs) { vmState = response.state if (options?.asyncFunctions && response.asyncFunctionName in options.asyncFunctions) { - const result = await options?.asyncFunctions[response.asyncFunctionName](...response.asyncFunctionArgs) - vmState.stack.push(result) + const result = await options?.asyncFunctions[response.asyncFunctionName]( + ...response.asyncFunctionArgs.map(convertHogToJS) + ) + vmState.stack.push(convertJSToHog(result)) } else if (response.asyncFunctionName in ASYNC_STL) { const result = await ASYNC_STL[response.asyncFunctionName]( response.asyncFunctionArgs, @@ -333,7 +335,7 @@ export function exec(code: any[] | VMState, options?: ExecOptions): ExecResult { .fill(null) .map(() => popStack()) if (options?.functions && options.functions[name] && name !== 'toString') { - stack.push(options.functions[name](...args)) + stack.push(convertJSToHog(options.functions[name](...args.map(convertHogToJS)))) } else if ( name !== 'toString' && ((options?.asyncFunctions && options.asyncFunctions[name]) || name in ASYNC_STL) diff --git a/latest_migrations.manifest b/latest_migrations.manifest index 32da32018dacd..905aeb627006b 100644 --- a/latest_migrations.manifest +++ b/latest_migrations.manifest @@ -5,7 +5,7 @@ contenttypes: 0002_remove_content_type_name ee: 0016_rolemembership_organization_member otp_static: 0002_throttling otp_totp: 0002_auto_20190420_0723 -posthog: 0424_survey_current_iteration_and_more +posthog: 0426_externaldatasource_sync_frequency sessions: 0001_initial social_django: 0010_uid_db_index two_factor: 0007_auto_20201201_1019 diff --git a/mypy-baseline.txt b/mypy-baseline.txt index 6a12d0a8cd137..d3e4f2d3fc605 100644 --- a/mypy-baseline.txt +++ b/mypy-baseline.txt @@ -3,6 +3,45 @@ posthog/temporal/common/utils.py:0: note: This is likely because "from_activity" posthog/temporal/common/utils.py:0: error: Argument 2 to "__get__" of "classmethod" has incompatible type "type[HeartbeatType]"; expected "type[Never]" [arg-type] posthog/warehouse/models/ssh_tunnel.py:0: error: Incompatible types in assignment (expression has type "NoEncryption", variable has type "BestAvailableEncryption") [assignment] posthog/temporal/data_imports/pipelines/zendesk/talk_api.py:0: error: Incompatible types in assignment (expression has type "None", variable has type "str") [assignment] +posthog/temporal/data_imports/pipelines/rest_source/config_setup.py:0: error: Dict entry 2 has incompatible type "Literal['auto']": "None"; expected "Literal['json_response', 'header_link', 'auto', 'single_page', 'cursor', 'offset', 'page_number']": "type[BasePaginator]" [dict-item] +posthog/temporal/data_imports/pipelines/rest_source/config_setup.py:0: error: Incompatible types in assignment (expression has type "None", variable has type "AuthConfigBase") [assignment] +posthog/temporal/data_imports/pipelines/rest_source/config_setup.py:0: error: Argument 1 to "get_auth_class" has incompatible type "Literal['bearer', 'api_key', 'http_basic'] | None"; expected "Literal['bearer', 'api_key', 'http_basic']" [arg-type] +posthog/temporal/data_imports/pipelines/rest_source/config_setup.py:0: error: Need type annotation for "dependency_graph" [var-annotated] +posthog/temporal/data_imports/pipelines/rest_source/config_setup.py:0: error: Incompatible types in assignment (expression has type "None", target has type "ResolvedParam") [assignment] +posthog/temporal/data_imports/pipelines/rest_source/config_setup.py:0: error: Incompatible return value type (got "tuple[TopologicalSorter[Any], dict[str, EndpointResource], dict[str, ResolvedParam]]", expected "tuple[Any, dict[str, EndpointResource], dict[str, ResolvedParam | None]]") [return-value] +posthog/temporal/data_imports/pipelines/rest_source/config_setup.py:0: error: Unsupported right operand type for in ("str | Endpoint | None") [operator] +posthog/temporal/data_imports/pipelines/rest_source/config_setup.py:0: error: Value of type variable "StrOrLiteralStr" of "parse" of "Formatter" cannot be "str | None" [type-var] +posthog/temporal/data_imports/pipelines/rest_source/config_setup.py:0: error: Unsupported right operand type for in ("dict[str, ResolveParamConfig | IncrementalParamConfig | Any] | None") [operator] +posthog/temporal/data_imports/pipelines/rest_source/config_setup.py:0: error: Unsupported right operand type for in ("dict[str, ResolveParamConfig | IncrementalParamConfig | Any] | None") [operator] +posthog/temporal/data_imports/pipelines/rest_source/config_setup.py:0: error: Value of type "dict[str, ResolveParamConfig | IncrementalParamConfig | Any] | None" is not indexable [index] +posthog/temporal/data_imports/pipelines/rest_source/config_setup.py:0: error: Item "None" of "dict[str, ResolveParamConfig | IncrementalParamConfig | Any] | None" has no attribute "pop" [union-attr] +posthog/temporal/data_imports/pipelines/rest_source/config_setup.py:0: error: Value of type "dict[str, ResolveParamConfig | IncrementalParamConfig | Any] | None" is not indexable [index] +posthog/temporal/data_imports/pipelines/rest_source/config_setup.py:0: error: Item "None" of "str | None" has no attribute "format" [union-attr] +posthog/temporal/data_imports/pipelines/rest_source/config_setup.py:0: error: Argument 1 to "single_entity_path" has incompatible type "str | None"; expected "str" [arg-type] +posthog/temporal/data_imports/pipelines/rest_source/config_setup.py:0: error: Item "None" of "dict[str, ResolveParamConfig | IncrementalParamConfig | Any] | None" has no attribute "items" [union-attr] +posthog/temporal/data_imports/pipelines/rest_source/config_setup.py:0: error: Incompatible types in assignment (expression has type "str | None", variable has type "str") [assignment] +posthog/temporal/data_imports/pipelines/rest_source/config_setup.py:0: error: Incompatible types in assignment (expression has type "str | None", variable has type "str") [assignment] +posthog/temporal/data_imports/pipelines/rest_source/config_setup.py:0: error: Statement is unreachable [unreachable] +posthog/temporal/data_imports/pipelines/rest_source/config_setup.py:0: error: Unpacked dict entry 0 has incompatible type "dict[str, Any] | None"; expected "SupportsKeysAndGetItem[str, Any]" [dict-item] +posthog/temporal/data_imports/pipelines/rest_source/config_setup.py:0: error: Unpacked dict entry 1 has incompatible type "dict[str, Any] | None"; expected "SupportsKeysAndGetItem[str, Any]" [dict-item] +posthog/temporal/data_imports/pipelines/rest_source/config_setup.py:0: error: Unpacked dict entry 0 has incompatible type "dict[str, Any] | None"; expected "SupportsKeysAndGetItem[str, ResolveParamConfig | IncrementalParamConfig | Any]" [dict-item] +posthog/temporal/data_imports/pipelines/rest_source/config_setup.py:0: error: Unpacked dict entry 1 has incompatible type "dict[str, ResolveParamConfig | IncrementalParamConfig | Any] | None"; expected "SupportsKeysAndGetItem[str, ResolveParamConfig | IncrementalParamConfig | Any]" [dict-item] +posthog/temporal/data_imports/pipelines/rest_source/__init__.py:0: error: Not all union combinations were tried because there are too many unions [misc] +posthog/temporal/data_imports/pipelines/rest_source/__init__.py:0: error: Argument 2 to "source" has incompatible type "str | None"; expected "str" [arg-type] +posthog/temporal/data_imports/pipelines/rest_source/__init__.py:0: error: Argument 3 to "source" has incompatible type "str | None"; expected "str" [arg-type] +posthog/temporal/data_imports/pipelines/rest_source/__init__.py:0: error: Argument 4 to "source" has incompatible type "int | None"; expected "int" [arg-type] +posthog/temporal/data_imports/pipelines/rest_source/__init__.py:0: error: Argument 6 to "source" has incompatible type "Schema | None"; expected "Schema" [arg-type] +posthog/temporal/data_imports/pipelines/rest_source/__init__.py:0: error: Argument 7 to "source" has incompatible type "Literal['evolve', 'discard_value', 'freeze', 'discard_row'] | TSchemaContractDict | None"; expected "Literal['evolve', 'discard_value', 'freeze', 'discard_row'] | TSchemaContractDict" [arg-type] +posthog/temporal/data_imports/pipelines/rest_source/__init__.py:0: error: Argument 8 to "source" has incompatible type "type[BaseConfiguration] | None"; expected "type[BaseConfiguration]" [arg-type] +posthog/temporal/data_imports/pipelines/rest_source/__init__.py:0: error: Argument 1 to "build_resource_dependency_graph" has incompatible type "EndpointResourceBase | None"; expected "EndpointResourceBase" [arg-type] +posthog/temporal/data_imports/pipelines/rest_source/__init__.py:0: error: Need type annotation for "resources" (hint: "resources: dict[, ] = ...") [var-annotated] +posthog/temporal/data_imports/pipelines/rest_source/__init__.py:0: error: Incompatible types in assignment (expression has type "ResolvedParam | None", variable has type "ResolvedParam") [assignment] +posthog/temporal/data_imports/pipelines/rest_source/__init__.py:0: error: Incompatible types in assignment (expression has type "list[str] | None", variable has type "list[str]") [assignment] +posthog/temporal/data_imports/pipelines/rest_source/__init__.py:0: error: Argument 1 to "setup_incremental_object" has incompatible type "dict[str, ResolveParamConfig | IncrementalParamConfig | Any] | None"; expected "dict[str, Any]" [arg-type] +posthog/temporal/data_imports/pipelines/rest_source/__init__.py:0: error: Statement is unreachable [unreachable] +posthog/temporal/data_imports/pipelines/rest_source/__init__.py:0: error: Argument 1 to "exclude_keys" has incompatible type "dict[str, ResolveParamConfig | IncrementalParamConfig | Any] | None"; expected "Mapping[str, Any]" [arg-type] +posthog/temporal/data_imports/pipelines/rest_source/__init__.py:0: error: Incompatible default for argument "incremental_param" (default has type "IncrementalParam | None", argument has type "IncrementalParam") [assignment] +posthog/temporal/data_imports/pipelines/rest_source/__init__.py:0: error: Argument "module" to "SourceInfo" has incompatible type Module | None; expected Module [arg-type] posthog/hogql/database/schema/numbers.py:0: error: Incompatible types in assignment (expression has type "dict[str, IntegerDatabaseField]", variable has type "dict[str, FieldOrTable]") [assignment] posthog/hogql/database/schema/numbers.py:0: note: "Dict" is invariant -- see https://mypy.readthedocs.io/en/stable/common_issues.html#variance posthog/hogql/database/schema/numbers.py:0: note: Consider using "Mapping" instead, which is covariant in the value type @@ -700,7 +739,6 @@ posthog/temporal/tests/batch_exports/test_snowflake_batch_export_workflow.py:0: posthog/temporal/tests/batch_exports/test_snowflake_batch_export_workflow.py:0: error: List item 0 has incompatible type "tuple[str, str, int, int, int, int, str, int]"; expected "tuple[str, str, int, int, str, str, str, str]" [list-item] posthog/temporal/tests/batch_exports/test_s3_batch_export_workflow.py:0: error: "tuple[Any, ...]" has no attribute "last_uploaded_part_timestamp" [attr-defined] posthog/temporal/tests/batch_exports/test_s3_batch_export_workflow.py:0: error: "tuple[Any, ...]" has no attribute "upload_state" [attr-defined] -posthog/temporal/data_imports/pipelines/test/test_pipeline.py:0: error: Argument "run_id" to "PipelineInputs" has incompatible type "UUID"; expected "str" [arg-type] posthog/migrations/0237_remove_timezone_from_teams.py:0: error: Argument 2 to "RunPython" has incompatible type "Callable[[Migration, Any], None]"; expected "_CodeCallable | None" [arg-type] posthog/migrations/0228_fix_tile_layouts.py:0: error: Argument 2 to "RunPython" has incompatible type "Callable[[Migration, Any], None]"; expected "_CodeCallable | None" [arg-type] posthog/api/plugin_log_entry.py:0: error: Name "timezone.datetime" is not defined [name-defined] @@ -709,11 +747,6 @@ posthog/api/plugin_log_entry.py:0: error: Name "timezone.datetime" is not define posthog/api/plugin_log_entry.py:0: error: Module "django.utils.timezone" does not explicitly export attribute "datetime" [attr-defined] posthog/api/action.py:0: error: Argument 1 to has incompatible type "*tuple[str, ...]"; expected "type[BaseRenderer]" [arg-type] posthog/temporal/tests/batch_exports/test_redshift_batch_export_workflow.py:0: error: Incompatible types in assignment (expression has type "str | int", variable has type "int") [assignment] -posthog/temporal/tests/external_data/test_external_data_job.py:0: error: Argument "run_id" to "ImportDataActivityInputs" has incompatible type "UUID"; expected "str" [arg-type] -posthog/temporal/tests/external_data/test_external_data_job.py:0: error: Argument "run_id" to "ImportDataActivityInputs" has incompatible type "UUID"; expected "str" [arg-type] -posthog/temporal/tests/external_data/test_external_data_job.py:0: error: Argument "run_id" to "ImportDataActivityInputs" has incompatible type "UUID"; expected "str" [arg-type] -posthog/temporal/tests/external_data/test_external_data_job.py:0: error: Argument "run_id" to "ImportDataActivityInputs" has incompatible type "UUID"; expected "str" [arg-type] -posthog/temporal/tests/external_data/test_external_data_job.py:0: error: Argument "run_id" to "ImportDataActivityInputs" has incompatible type "UUID"; expected "str" [arg-type] posthog/api/test/batch_exports/conftest.py:0: error: Argument "activities" to "ThreadedWorker" has incompatible type "list[function]"; expected "Sequence[Callable[..., Any]]" [arg-type] posthog/api/test/test_team.py:0: error: "HttpResponse" has no attribute "json" [attr-defined] posthog/api/test/test_team.py:0: error: "HttpResponse" has no attribute "json" [attr-defined] diff --git a/package.json b/package.json index f075f3dabba46..63f1cdcce3688 100644 --- a/package.json +++ b/package.json @@ -146,7 +146,7 @@ "pmtiles": "^2.11.0", "postcss": "^8.4.31", "postcss-preset-env": "^9.3.0", - "posthog-js": "1.138.2", + "posthog-js": "1.139.0", "posthog-js-lite": "3.0.0", "prettier": "^2.8.8", "prop-types": "^15.7.2", diff --git a/plugin-server/package.json b/plugin-server/package.json index e5766fa3d44f5..3344291ce0d1d 100644 --- a/plugin-server/package.json +++ b/plugin-server/package.json @@ -50,6 +50,7 @@ "@google-cloud/storage": "^5.8.5", "@maxmind/geoip2-node": "^3.4.0", "@posthog/clickhouse": "^1.7.0", + "@posthog/hogvm": "^1.0.11", "@posthog/plugin-scaffold": "1.4.4", "@sentry/node": "^7.49.0", "@sentry/profiling-node": "^0.3.0", diff --git a/plugin-server/pnpm-lock.yaml b/plugin-server/pnpm-lock.yaml index 05fb5a15d84bd..af85a11df6436 100644 --- a/plugin-server/pnpm-lock.yaml +++ b/plugin-server/pnpm-lock.yaml @@ -43,6 +43,9 @@ dependencies: '@posthog/clickhouse': specifier: ^1.7.0 version: 1.7.0 + '@posthog/hogvm': + specifier: ^1.0.11 + version: 1.0.11 '@posthog/plugin-scaffold': specifier: 1.4.4 version: 1.4.4 @@ -3104,6 +3107,10 @@ packages: engines: {node: '>=12'} dev: false + /@posthog/hogvm@1.0.11: + resolution: {integrity: sha512-W1m4UPmpaNwm9+Rwpb3rjuZd3z+/gO9MsxibCnxdTndrFgIrNjGOas2ZEpZqJblV3sgubFbGq6IXdORbM+nv5w==} + dev: false + /@posthog/plugin-scaffold@1.4.4: resolution: {integrity: sha512-3z1ENm1Ys5lEQil0H7TVOqHvD24+ydiZFk5hggpbHRx1iOxAK+Eu5qFyAROwPUcCo7NOYjmH2xL1C4B1vaHilg==} dependencies: diff --git a/plugin-server/src/capabilities.ts b/plugin-server/src/capabilities.ts index cda8da5b20abd..47d30482bd72d 100644 --- a/plugin-server/src/capabilities.ts +++ b/plugin-server/src/capabilities.ts @@ -23,6 +23,7 @@ export function getPluginServerCapabilities(config: PluginsServerConfig): Plugin personOverrides: true, appManagementSingleton: true, preflightSchedules: true, + cdpProcessedEvents: true, ...sharedCapabilities, } case PluginServerMode.ingestion: @@ -87,5 +88,11 @@ export function getPluginServerCapabilities(config: PluginsServerConfig): Plugin personOverrides: true, ...sharedCapabilities, } + + case PluginServerMode.cdp_processed_events: + return { + cdpProcessedEvents: true, + ...sharedCapabilities, + } } } diff --git a/plugin-server/src/config/config.ts b/plugin-server/src/config/config.ts index f3584ded83880..3ab2cb79a536d 100644 --- a/plugin-server/src/config/config.ts +++ b/plugin-server/src/config/config.ts @@ -137,7 +137,6 @@ export function getDefaultConfig(): PluginsServerConfig { RUSTY_HOOK_ROLLOUT_PERCENTAGE: 0, RUSTY_HOOK_URL: '', CAPTURE_CONFIG_REDIS_HOST: null, - LAZY_PERSON_CREATION_TEAMS: '', STARTUP_PROFILE_DURATION_SECONDS: 300, // 5 minutes STARTUP_PROFILE_CPU: false, diff --git a/plugin-server/src/main/ingestion-queues/session-recording/session-recordings-consumer.ts b/plugin-server/src/main/ingestion-queues/session-recording/session-recordings-consumer.ts index 8d7f2d79da3b9..3be63ed2bb0c6 100644 --- a/plugin-server/src/main/ingestion-queues/session-recording/session-recordings-consumer.ts +++ b/plugin-server/src/main/ingestion-queues/session-recording/session-recordings-consumer.ts @@ -333,11 +333,13 @@ export class SessionRecordingIngester { } public async handleEachBatch(messages: Message[], heartbeat: () => void): Promise { - status.info('🔁', `blob_ingester_consumer - handling batch`, { - size: messages.length, - partitionsInBatch: [...new Set(messages.map((x) => x.partition))], - assignedPartitions: this.assignedPartitions, - }) + if (messages.length !== 0) { + status.info('🔁', `blob_ingester_consumer - handling batch`, { + size: messages.length, + partitionsInBatch: [...new Set(messages.map((x) => x.partition))], + assignedPartitions: this.assignedPartitions, + }) + } await runInstrumentedFunction({ statsKey: `recordingingester.handleEachBatch`, sendTimeoutGuardToSentry: false, diff --git a/plugin-server/src/main/pluginsServer.ts b/plugin-server/src/main/pluginsServer.ts index 3a1ba51f87992..4cc219522003f 100644 --- a/plugin-server/src/main/pluginsServer.ts +++ b/plugin-server/src/main/pluginsServer.ts @@ -10,7 +10,8 @@ import { Counter } from 'prom-client' import v8Profiler from 'v8-profiler-next' import { getPluginServerCapabilities } from '../capabilities' -import { buildIntegerMatcher, defaultConfig, sessionRecordingConsumerConfig } from '../config/config' +import { CdpProcessedEventsConsumer } from '../cdp/cdp-processed-events-consumer' +import { defaultConfig, sessionRecordingConsumerConfig } from '../config/config' import { Hub, PluginServerCapabilities, PluginsServerConfig } from '../types' import { createHub, createKafkaClient, createKafkaProducerWrapper } from '../utils/db/hub' import { PostgresRouter } from '../utils/db/postgres' @@ -105,6 +106,8 @@ export async function startPluginsServer( let onEventHandlerConsumer: KafkaJSIngestionConsumer | undefined let stopWebhooksHandlerConsumer: () => Promise | undefined + const shutdownCallbacks: (() => Promise)[] = [] + // Kafka consumer. Handles events that we couldn't find an existing person // to associate. The buffer handles delaying the ingestion of these events // (default 60 seconds) to allow for the person to be created in the @@ -157,6 +160,7 @@ export async function startPluginsServer( stopSessionRecordingBlobOverflowConsumer?.(), schedulerTasksConsumer?.disconnect(), personOverridesPeriodicTask?.stop(), + ...shutdownCallbacks.map((cb) => cb()), ]) if (piscina) { @@ -370,14 +374,7 @@ export async function startPluginsServer( const teamManager = hub?.teamManager ?? new TeamManager(postgres, serverConfig) const organizationManager = hub?.organizationManager ?? new OrganizationManager(postgres, teamManager) const KafkaProducerWrapper = hub?.kafkaProducer ?? (await createKafkaProducerWrapper(serverConfig)) - const rustyHook = - hub?.rustyHook ?? - new RustyHook( - buildIntegerMatcher(serverConfig.RUSTY_HOOK_FOR_TEAMS, true), - serverConfig.RUSTY_HOOK_ROLLOUT_PERCENTAGE, - serverConfig.RUSTY_HOOK_URL, - serverConfig.EXTERNAL_REQUEST_TIMEOUT_MS - ) + const rustyHook = hub?.rustyHook ?? new RustyHook(serverConfig) const appMetrics = hub?.appMetrics ?? new AppMetrics( @@ -494,6 +491,21 @@ export async function startPluginsServer( } } + if (capabilities.cdpProcessedEvents) { + ;[hub, closeHub] = hub ? [hub, closeHub] : await createHub(serverConfig, capabilities) + const consumer = new CdpProcessedEventsConsumer(serverConfig, hub) + await consumer.start() + + if (consumer.batchConsumer) { + shutdownOnConsumerExit(consumer.batchConsumer) + } + + shutdownCallbacks.push(async () => { + await consumer.stop() + }) + healthChecks['cdp-processed-events'] = () => consumer.isHealthy() ?? false + } + if (capabilities.personOverrides) { const postgres = hub?.postgres ?? new PostgresRouter(serverConfig) const kafkaProducer = hub?.kafkaProducer ?? (await createKafkaProducerWrapper(serverConfig)) diff --git a/plugin-server/src/types.ts b/plugin-server/src/types.ts index 7fffd6930bac2..78996b2a4fad9 100644 --- a/plugin-server/src/types.ts +++ b/plugin-server/src/types.ts @@ -81,6 +81,7 @@ export enum PluginServerMode { recordings_blob_ingestion = 'recordings-blob-ingestion', recordings_blob_ingestion_overflow = 'recordings-blob-ingestion-overflow', person_overrides = 'person-overrides', + cdp_processed_events = 'cdp-processed-events', } export const stringToPluginServerMode = Object.fromEntries( @@ -211,7 +212,6 @@ export interface PluginsServerConfig { SKIP_UPDATE_EVENT_AND_PROPERTIES_STEP: boolean PIPELINE_STEP_STALLED_LOG_TIMEOUT: number CAPTURE_CONFIG_REDIS_HOST: string | null // Redis cluster to use to coordinate with capture (overflow, routing) - LAZY_PERSON_CREATION_TEAMS: string // dump profiles to disk, covering the first N seconds of runtime STARTUP_PROFILE_DURATION_SECONDS: number @@ -298,7 +298,6 @@ export interface Hub extends PluginsServerConfig { pluginConfigsToSkipElementsParsing: ValueMatcher poeEmbraceJoinForTeams: ValueMatcher poeWritesExcludeTeams: ValueMatcher - lazyPersonCreationTeams: ValueMatcher // lookups eventsToDropByToken: Map } @@ -315,6 +314,7 @@ export interface PluginServerCapabilities { processAsyncWebhooksHandlers?: boolean sessionRecordingBlobIngestion?: boolean sessionRecordingBlobOverflowIngestion?: boolean + cdpProcessedEvents?: boolean personOverrides?: boolean appManagementSingleton?: boolean preflightSchedules?: boolean // Used for instance health checks on hobby deploy, not useful on cloud diff --git a/plugin-server/src/utils/db/db.ts b/plugin-server/src/utils/db/db.ts index 500044a815e90..a7cd6d0b23dd9 100644 --- a/plugin-server/src/utils/db/db.ts +++ b/plugin-server/src/utils/db/db.ts @@ -754,20 +754,14 @@ export class DB { personUpdateVersionMismatchCounter.inc() } - const kafkaMessages = [] - const message = generateKafkaPersonUpdateMessage(updatedPerson) - if (tx) { - kafkaMessages.push(message) - } else { - await this.kafkaProducer.queueMessage({ kafkaMessage: message, waitForAck: true }) - } + const kafkaMessage = generateKafkaPersonUpdateMessage(updatedPerson) status.debug( '🧑‍🦰', `Updated person ${updatedPerson.uuid} of team ${updatedPerson.team_id} to version ${updatedPerson.version}.` ) - return [updatedPerson, kafkaMessages] + return [updatedPerson, [kafkaMessage]] } public async deletePerson(person: InternalPerson, tx?: TransactionClient): Promise { diff --git a/plugin-server/src/utils/db/hub.ts b/plugin-server/src/utils/db/hub.ts index 331f95c95d6bb..3feaf4cd63c63 100644 --- a/plugin-server/src/utils/db/hub.ts +++ b/plugin-server/src/utils/db/hub.ts @@ -146,12 +146,7 @@ export async function createHub( const organizationManager = new OrganizationManager(postgres, teamManager) const pluginsApiKeyManager = new PluginsApiKeyManager(db) const rootAccessManager = new RootAccessManager(db) - const rustyHook = new RustyHook( - buildIntegerMatcher(serverConfig.RUSTY_HOOK_FOR_TEAMS, true), - serverConfig.RUSTY_HOOK_ROLLOUT_PERCENTAGE, - serverConfig.RUSTY_HOOK_URL, - serverConfig.EXTERNAL_REQUEST_TIMEOUT_MS - ) + const rustyHook = new RustyHook(serverConfig) const actionManager = new ActionManager(postgres, serverConfig) const actionMatcher = new ActionMatcher(postgres, actionManager, teamManager) @@ -209,7 +204,6 @@ export async function createHub( pluginConfigsToSkipElementsParsing: buildIntegerMatcher(process.env.SKIP_ELEMENTS_PARSING_PLUGINS, true), poeEmbraceJoinForTeams: buildIntegerMatcher(process.env.POE_EMBRACE_JOIN_FOR_TEAMS, true), poeWritesExcludeTeams: buildIntegerMatcher(process.env.POE_WRITES_EXCLUDE_TEAMS, false), - lazyPersonCreationTeams: buildIntegerMatcher(process.env.LAZY_PERSON_CREATION_TEAMS, true), eventsToDropByToken: createEventsToDropByToken(process.env.DROP_EVENTS_BY_TOKEN_DISTINCT_ID), } diff --git a/plugin-server/src/worker/ingestion/event-pipeline/processPersonsStep.ts b/plugin-server/src/worker/ingestion/event-pipeline/processPersonsStep.ts index a0978497d7e34..377981fe64b09 100644 --- a/plugin-server/src/worker/ingestion/event-pipeline/processPersonsStep.ts +++ b/plugin-server/src/worker/ingestion/event-pipeline/processPersonsStep.ts @@ -10,22 +10,21 @@ export async function processPersonsStep( event: PluginEvent, timestamp: DateTime, processPerson: boolean -): Promise<[PluginEvent, Person]> { +): Promise<[PluginEvent, Person, Promise]> { let overridesWriter: DeferredPersonOverrideWriter | undefined = undefined if (runner.poEEmbraceJoin) { overridesWriter = new DeferredPersonOverrideWriter(runner.hub.db.postgres) } - const person = await new PersonState( + const [person, kafkaAck] = await new PersonState( event, event.team_id, String(event.distinct_id), timestamp, processPerson, runner.hub.db, - runner.hub.lazyPersonCreationTeams(event.team_id), overridesWriter ).update() - return [event, person] + return [event, person, kafkaAck] } diff --git a/plugin-server/src/worker/ingestion/event-pipeline/runner.ts b/plugin-server/src/worker/ingestion/event-pipeline/runner.ts index 52e762949a924..26c645e5089ca 100644 --- a/plugin-server/src/worker/ingestion/event-pipeline/runner.ts +++ b/plugin-server/src/worker/ingestion/event-pipeline/runner.ts @@ -175,19 +175,17 @@ export class EventPipelineRunner { } if (event.event === '$$client_ingestion_warning') { - kafkaAcks.push( - captureIngestionWarning( - this.hub.db.kafkaProducer, - event.team_id, - 'client_ingestion_warning', - { - eventUuid: event.uuid, - event: event.event, - distinctId: event.distinct_id, - message: event.properties?.$$client_ingestion_warning_message, - }, - { alwaysSend: true } - ) + await captureIngestionWarning( + this.hub.db.kafkaProducer, + event.team_id, + 'client_ingestion_warning', + { + eventUuid: event.uuid, + event: event.event, + distinctId: event.distinct_id, + message: event.properties?.$$client_ingestion_warning_message, + }, + { alwaysSend: true } ) return this.registerLastStep('clientIngestionWarning', [event], kafkaAcks) @@ -205,11 +203,12 @@ export class EventPipelineRunner { event.team_id ) - const [postPersonEvent, person] = await this.runStep( + const [postPersonEvent, person, personKafkaAck] = await this.runStep( processPersonsStep, [this, normalizedEvent, timestamp, processPerson], event.team_id ) + kafkaAcks.push(personKafkaAck) const preparedEvent = await this.runStep( prepareEventStep, diff --git a/plugin-server/src/worker/ingestion/person-state.ts b/plugin-server/src/worker/ingestion/person-state.ts index 3f9cfa2f9d3b6..d3bf32e21310b 100644 --- a/plugin-server/src/worker/ingestion/person-state.ts +++ b/plugin-server/src/worker/ingestion/person-state.ts @@ -92,7 +92,6 @@ export class PersonState { private timestamp: DateTime, private processPerson: boolean, // $process_person_profile flag from the event private db: DB, - private lazyPersonCreation: boolean, private personOverrideWriter?: DeferredPersonOverrideWriter ) { this.eventProperties = event.properties! @@ -102,68 +101,62 @@ export class PersonState { this.updateIsIdentified = false } - async update(): Promise { + async update(): Promise<[Person, Promise]> { if (!this.processPerson) { - if (this.lazyPersonCreation) { - const existingPerson = await this.db.fetchPerson(this.teamId, this.distinctId, { useReadReplica: true }) - if (existingPerson) { - const person = existingPerson as Person - - // Ensure person properties don't propagate elsewhere, such as onto the event itself. - person.properties = {} - - if (this.timestamp > person.created_at.plus({ minutes: 1 })) { - // See documentation on the field. - // - // Note that we account for timestamp vs person creation time (with a little - // padding for good measure) to account for ingestion lag. It's possible for - // events to be processed after person creation even if they were sent prior - // to person creation, and the user did nothing wrong in that case. - person.force_upgrade = true - } - - return person - } - - // We need a value from the `person_created_column` in ClickHouse. This should be - // hidden from users for events without a real person, anyway. It's slightly offset - // from the 0 date (by 5 seconds) in order to assist in debugging by being - // harmlessly distinct from Unix UTC "0". - const createdAt = DateTime.utc(1970, 1, 1, 0, 0, 5) - - const fakePerson: Person = { - team_id: this.teamId, - properties: {}, - uuid: uuidFromDistinctId(this.teamId, this.distinctId), - created_at: createdAt, - } - return fakePerson - } else { - // We don't need to handle any properties for `processPerson=false` events, so we can - // short circuit by just finding or creating a person and returning early. - const [person, _] = await promiseRetry(() => this.createOrGetPerson(), 'get_person_personless') + const existingPerson = await this.db.fetchPerson(this.teamId, this.distinctId, { useReadReplica: true }) + if (existingPerson) { + const person = existingPerson as Person // Ensure person properties don't propagate elsewhere, such as onto the event itself. person.properties = {} - return person + if (this.timestamp > person.created_at.plus({ minutes: 1 })) { + // See documentation on the field. + // + // Note that we account for timestamp vs person creation time (with a little + // padding for good measure) to account for ingestion lag. It's possible for + // events to be processed after person creation even if they were sent prior + // to person creation, and the user did nothing wrong in that case. + person.force_upgrade = true + } + + return [person, Promise.resolve()] } + + // We need a value from the `person_created_column` in ClickHouse. This should be + // hidden from users for events without a real person, anyway. It's slightly offset + // from the 0 date (by 5 seconds) in order to assist in debugging by being + // harmlessly distinct from Unix UTC "0". + const createdAt = DateTime.utc(1970, 1, 1, 0, 0, 5) + + const fakePerson: Person = { + team_id: this.teamId, + properties: {}, + uuid: uuidFromDistinctId(this.teamId, this.distinctId), + created_at: createdAt, + } + return [fakePerson, Promise.resolve()] } - const person: InternalPerson | undefined = await this.handleIdentifyOrAlias() // TODO: make it also return a boolean for if we can exit early here + const [person, identifyOrAliasKafkaAck]: [InternalPerson | undefined, Promise] = + await this.handleIdentifyOrAlias() // TODO: make it also return a boolean for if we can exit early here + if (person) { // try to shortcut if we have the person from identify or alias try { - return await this.updatePersonProperties(person) + const [updatedPerson, updateKafkaAck] = await this.updatePersonProperties(person) + return [updatedPerson, Promise.all([identifyOrAliasKafkaAck, updateKafkaAck]).then(() => undefined)] } catch (error) { // shortcut didn't work, swallow the error and try normal retry loop below status.debug('🔁', `failed update after adding distinct IDs, retrying`, { error }) } } - return await this.handleUpdate() + + const [updatedPerson, updateKafkaAck] = await this.handleUpdate() + return [updatedPerson, Promise.all([identifyOrAliasKafkaAck, updateKafkaAck]).then(() => undefined)] } - async handleUpdate(): Promise { + async handleUpdate(): Promise<[InternalPerson, Promise]> { // There are various reasons why update can fail: // - anothe thread created the person during a race // - the person might have been merged between start of processing and now @@ -171,10 +164,10 @@ export class PersonState { return await promiseRetry(() => this.updateProperties(), 'update_person') } - async updateProperties(): Promise { + async updateProperties(): Promise<[InternalPerson, Promise]> { const [person, propertiesHandled] = await this.createOrGetPerson() if (propertiesHandled) { - return person + return [person, Promise.resolve()] } return await this.updatePersonProperties(person) } @@ -251,7 +244,7 @@ export class PersonState { ) } - private async updatePersonProperties(person: InternalPerson): Promise { + private async updatePersonProperties(person: InternalPerson): Promise<[InternalPerson, Promise]> { person.properties ||= {} const update: Partial = {} @@ -263,10 +256,12 @@ export class PersonState { } if (Object.keys(update).length > 0) { - // Note: we're not passing the client, so kafka messages are waited for within the function - ;[person] = await this.db.updatePersonDeprecated(person, update) + const [updatedPerson, kafkaMessages] = await this.db.updatePersonDeprecated(person, update) + const kafkaAck = this.db.kafkaProducer.queueMessages({ kafkaMessages, waitForAck: true }) + return [updatedPerson, kafkaAck] } - return person + + return [person, Promise.resolve()] } /** @@ -306,7 +301,7 @@ export class PersonState { // Alias & merge - async handleIdentifyOrAlias(): Promise { + async handleIdentifyOrAlias(): Promise<[InternalPerson | undefined, Promise]> { /** * strategy: * - if the two distinct ids passed don't match and aren't illegal, then mark `is_identified` to be true for the `distinct_id` person @@ -350,7 +345,7 @@ export class PersonState { } finally { clearTimeout(timeout) } - return undefined + return [undefined, Promise.resolve()] } public async merge( @@ -358,10 +353,10 @@ export class PersonState { mergeIntoDistinctId: string, teamId: number, timestamp: DateTime - ): Promise { + ): Promise<[InternalPerson | undefined, Promise]> { // No reason to alias person against itself. Done by posthog-node when updating user properties if (mergeIntoDistinctId === otherPersonDistinctId) { - return undefined + return [undefined, Promise.resolve()] } if (isDistinctIdIllegal(mergeIntoDistinctId)) { await captureIngestionWarning( @@ -375,7 +370,7 @@ export class PersonState { }, { alwaysSend: true } ) - return undefined + return [undefined, Promise.resolve()] } if (isDistinctIdIllegal(otherPersonDistinctId)) { await captureIngestionWarning( @@ -389,7 +384,7 @@ export class PersonState { }, { alwaysSend: true } ) - return undefined + return [undefined, Promise.resolve()] } return promiseRetry( () => this.mergeDistinctIds(otherPersonDistinctId, mergeIntoDistinctId, teamId, timestamp), @@ -402,7 +397,7 @@ export class PersonState { mergeIntoDistinctId: string, teamId: number, timestamp: DateTime - ): Promise { + ): Promise<[InternalPerson, Promise]> { this.updateIsIdentified = true const otherPerson = await this.db.fetchPerson(teamId, otherPersonDistinctId) @@ -412,8 +407,9 @@ export class PersonState { // Overrides are only created when the version is > 0, see: // https://github.com/PostHog/posthog/blob/92e17ce307a577c4233d4ab252eebc6c2207a5ee/posthog/models/person/sql.py#L269-L287 // - // With the addition of optional person processing, we are now rolling out a change to - // lazily create `posthog_persondistinctid` and `posthog_person` rows. This means that: + // With the addition of optional person processing, we are no longer creating + // `posthog_persondistinctid` and `posthog_person` rows when $process_person_profile=false. + // This means that: // 1. At merge time, it's possible this `distinct_id` and its deterministically generated // `person.uuid` has already been used for events in ClickHouse, but they have no // corresponding rows in the `posthog_persondistinctid` or `posthog_person` tables @@ -422,20 +418,17 @@ export class PersonState { // `distinct_id` even though we're just now INSERT-ing it into Postgres/ClickHouse. We do // this by starting with `version=1`, as if we had just deleted the old user and were // updating the `distinct_id` row as part of the merge - let addDistinctIdVersion = 0 - if (this.lazyPersonCreation) { - addDistinctIdVersion = 1 - } + const addDistinctIdVersion = 1 if (otherPerson && !mergeIntoPerson) { await this.db.addDistinctId(otherPerson, mergeIntoDistinctId, addDistinctIdVersion) - return otherPerson + return [otherPerson, Promise.resolve()] } else if (!otherPerson && mergeIntoPerson) { await this.db.addDistinctId(mergeIntoPerson, otherPersonDistinctId, addDistinctIdVersion) - return mergeIntoPerson + return [mergeIntoPerson, Promise.resolve()] } else if (otherPerson && mergeIntoPerson) { if (otherPerson.id == mergeIntoPerson.id) { - return mergeIntoPerson + return [mergeIntoPerson, Promise.resolve()] } return await this.mergePeople({ mergeInto: mergeIntoPerson, @@ -446,18 +439,21 @@ export class PersonState { } // The last case: (!oldPerson && !newPerson) - return await this.createPerson( - // TODO: in this case we could skip the properties updates later - timestamp, - this.eventProperties['$set'] || {}, - this.eventProperties['$set_once'] || {}, - teamId, - null, - true, - this.event.uuid, - [mergeIntoDistinctId, otherPersonDistinctId], - addDistinctIdVersion - ) + return [ + await this.createPerson( + // TODO: in this case we could skip the properties updates later + timestamp, + this.eventProperties['$set'] || {}, + this.eventProperties['$set_once'] || {}, + teamId, + null, + true, + this.event.uuid, + [mergeIntoDistinctId, otherPersonDistinctId], + addDistinctIdVersion + ), + Promise.resolve(), + ] } public async mergePeople({ @@ -470,7 +466,7 @@ export class PersonState { mergeIntoDistinctId: string otherPerson: InternalPerson otherPersonDistinctId: string - }): Promise { + }): Promise<[InternalPerson, Promise]> { const olderCreatedAt = DateTime.min(mergeInto.created_at, otherPerson.created_at) const mergeAllowed = this.isMergeAllowed(otherPerson) @@ -488,7 +484,7 @@ export class PersonState { { alwaysSend: true } ) status.warn('🤔', 'refused to merge an already identified user via an $identify or $create_alias call') - return mergeInto // We're returning the original person tied to distinct_id used for the event + return [mergeInto, Promise.resolve()] // We're returning the original person tied to distinct_id used for the event } // How the merge works: @@ -508,14 +504,14 @@ export class PersonState { const properties: Properties = { ...otherPerson.properties, ...mergeInto.properties } this.applyEventPropertyUpdates(properties) - const [kafkaMessages, mergedPerson] = await this.handleMergeTransaction( + const [mergedPerson, kafkaMessages] = await this.handleMergeTransaction( mergeInto, otherPerson, olderCreatedAt, // Keep the oldest created_at (i.e. the first time we've seen either person) properties ) - await this.db.kafkaProducer.queueMessages({ kafkaMessages, waitForAck: true }) - return mergedPerson + + return [mergedPerson, kafkaMessages] } private isMergeAllowed(mergeFrom: InternalPerson): boolean { @@ -529,7 +525,7 @@ export class PersonState { otherPerson: InternalPerson, createdAt: DateTime, properties: Properties - ): Promise<[ProducerRecord[], InternalPerson]> { + ): Promise<[InternalPerson, Promise]> { mergeTxnAttemptCounter .labels({ call: this.event.event, // $identify, $create_alias or $merge_dangerously @@ -539,7 +535,7 @@ export class PersonState { }) .inc() - const result: [ProducerRecord[], InternalPerson] = await this.db.postgres.transaction( + const [mergedPerson, kafkaMessages]: [InternalPerson, ProducerRecord[]] = await this.db.postgres.transaction( PostgresUse.COMMON_WRITE, 'mergePeople', async (tx) => { @@ -573,7 +569,7 @@ export class PersonState { ) } - return [[...updatePersonMessages, ...distinctIdMessages, ...deletePersonMessages], person] + return [person, [...updatePersonMessages, ...distinctIdMessages, ...deletePersonMessages]] } ) @@ -585,7 +581,10 @@ export class PersonState { poEEmbraceJoin: String(!!this.personOverrideWriter), }) .inc() - return result + + const kafkaAck = this.db.kafkaProducer.queueMessages({ kafkaMessages, waitForAck: true }) + + return [mergedPerson, kafkaAck] } } diff --git a/plugin-server/src/worker/ingestion/utils.ts b/plugin-server/src/worker/ingestion/utils.ts index 9488ee759581b..c6c313d74d459 100644 --- a/plugin-server/src/worker/ingestion/utils.ts +++ b/plugin-server/src/worker/ingestion/utils.ts @@ -94,7 +94,7 @@ export async function captureIngestionWarning( }, ], }, - waitForAck: true, + waitForAck: false, }) } else { return Promise.resolve() diff --git a/plugin-server/src/worker/rusty-hook.ts b/plugin-server/src/worker/rusty-hook.ts index cb829800cbaa3..a4d1c6c6b2d81 100644 --- a/plugin-server/src/worker/rusty-hook.ts +++ b/plugin-server/src/worker/rusty-hook.ts @@ -2,7 +2,8 @@ import { Webhook } from '@posthog/plugin-scaffold' import * as Sentry from '@sentry/node' import fetch from 'node-fetch' -import { ValueMatcher } from '../types' +import { buildIntegerMatcher } from '../config/config' +import { PluginsServerConfig, ValueMatcher } from '../types' import { isProdEnv } from '../utils/env-utils' import { raiseIfUserProvidedUrlUnsafe } from '../utils/fetch' import { status } from '../utils/status' @@ -23,12 +24,16 @@ interface RustyWebhookPayload { } export class RustyHook { + private enabledForTeams: ValueMatcher + constructor( - private enabledForTeams: ValueMatcher, - private rolloutPercentage: number, - private serviceUrl: string, - private requestTimeoutMs: number - ) {} + private serverConfig: Pick< + PluginsServerConfig, + 'RUSTY_HOOK_URL' | 'RUSTY_HOOK_FOR_TEAMS' | 'RUSTY_HOOK_ROLLOUT_PERCENTAGE' | 'EXTERNAL_REQUEST_TIMEOUT_MS' + > + ) { + this.enabledForTeams = buildIntegerMatcher(serverConfig.RUSTY_HOOK_FOR_TEAMS, true) + } public async enqueueIfEnabledForTeam({ webhook, @@ -43,7 +48,7 @@ export class RustyHook { }): Promise { // A simple and blunt rollout that just uses the last digits of the Team ID as a stable // selection against the `rolloutPercentage`. - const enabledByRolloutPercentage = (teamId % 1000) / 1000 < this.rolloutPercentage + const enabledByRolloutPercentage = (teamId % 1000) / 1000 < this.serverConfig.RUSTY_HOOK_ROLLOUT_PERCENTAGE if (!enabledByRolloutPercentage && !this.enabledForTeams(teamId)) { return false } @@ -75,14 +80,14 @@ export class RustyHook { const timer = new Date() try { attempt += 1 - const response = await fetch(this.serviceUrl, { + const response = await fetch(this.serverConfig.RUSTY_HOOK_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body, // Sure, it's not an external request, but we should have a timeout and this is as // good as any. - timeout: this.requestTimeoutMs, + timeout: this.serverConfig.EXTERNAL_REQUEST_TIMEOUT_MS, }) if (response.ok) { diff --git a/plugin-server/tests/main/db.test.ts b/plugin-server/tests/main/db.test.ts index e5c08a092099b..94b8d8d310363 100644 --- a/plugin-server/tests/main/db.test.ts +++ b/plugin-server/tests/main/db.test.ts @@ -360,7 +360,11 @@ describe('DB', () => { const personProvided = { ...personDbBefore, properties: { c: 'bbb' }, created_at: providedPersonTs } const updateTs = DateTime.fromISO('2000-04-04T11:42:06.502Z').toUTC() const update = { created_at: updateTs } - const [updatedPerson] = await db.updatePersonDeprecated(personProvided, update) + const [updatedPerson, kafkaMessages] = await db.updatePersonDeprecated(personProvided, update) + await hub.db.kafkaProducer.queueMessages({ + kafkaMessages, + waitForAck: true, + }) // verify we have the correct update in Postgres db const personDbAfter = await fetchPersonByPersonId(personDbBefore.team_id, personDbBefore.id) @@ -418,7 +422,13 @@ describe('DB', () => { await delayUntilEventIngested(fetchPersonsRows, 1) // We do an update to verify - await db.updatePersonDeprecated(person, { properties: { foo: 'bar' } }) + const [_p, updatePersonKafkaMessages] = await db.updatePersonDeprecated(person, { + properties: { foo: 'bar' }, + }) + await hub.db.kafkaProducer.queueMessages({ + kafkaMessages: updatePersonKafkaMessages, + waitForAck: true, + }) await db.kafkaProducer.flush() await delayUntilEventIngested(fetchPersonsRows, 2) diff --git a/plugin-server/tests/main/ingestion-queues/analytics-events-ingestion-overflow-consumer.test.ts b/plugin-server/tests/main/ingestion-queues/analytics-events-ingestion-overflow-consumer.test.ts index 774475a5b34aa..e0f46c4a39d31 100644 --- a/plugin-server/tests/main/ingestion-queues/analytics-events-ingestion-overflow-consumer.test.ts +++ b/plugin-server/tests/main/ingestion-queues/analytics-events-ingestion-overflow-consumer.test.ts @@ -113,7 +113,7 @@ describe('eachBatchParallelIngestion with overflow consume', () => { }, ], }, - waitForAck: true, + waitForAck: false, }) // Event is processed diff --git a/plugin-server/tests/main/ingestion-queues/session-recording/utils.test.ts b/plugin-server/tests/main/ingestion-queues/session-recording/utils.test.ts index edd4f95bebda2..6678c28d92a3e 100644 --- a/plugin-server/tests/main/ingestion-queues/session-recording/utils.test.ts +++ b/plugin-server/tests/main/ingestion-queues/session-recording/utils.test.ts @@ -221,7 +221,7 @@ describe('session-recording utils', () => { ], topic: 'clickhouse_ingestion_warnings_test', }, - waitForAck: true, + waitForAck: false, }, ], ], @@ -241,7 +241,7 @@ describe('session-recording utils', () => { ], topic: 'clickhouse_ingestion_warnings_test', }, - waitForAck: true, + waitForAck: false, }, ], ], diff --git a/plugin-server/tests/main/process-event.test.ts b/plugin-server/tests/main/process-event.test.ts index 71432287879c3..f5a9576d07c7c 100644 --- a/plugin-server/tests/main/process-event.test.ts +++ b/plugin-server/tests/main/process-event.test.ts @@ -209,11 +209,20 @@ test('merge people', async () => { const p0 = await createPerson(hub, team, ['person_0'], { $os: 'Microsoft' }) await delayUntilEventIngested(() => hub.db.fetchPersons(Database.ClickHouse), 1) - await hub.db.updatePersonDeprecated(p0, { created_at: DateTime.fromISO('2020-01-01T00:00:00Z') }) + const [_person0, kafkaMessages0] = await hub.db.updatePersonDeprecated(p0, { + created_at: DateTime.fromISO('2020-01-01T00:00:00Z'), + }) const p1 = await createPerson(hub, team, ['person_1'], { $os: 'Chrome', $browser: 'Chrome' }) await delayUntilEventIngested(() => hub.db.fetchPersons(Database.ClickHouse), 2) - await hub.db.updatePersonDeprecated(p1, { created_at: DateTime.fromISO('2019-07-01T00:00:00Z') }) + const [_person1, kafkaMessages1] = await hub.db.updatePersonDeprecated(p1, { + created_at: DateTime.fromISO('2019-07-01T00:00:00Z'), + }) + + await hub.db.kafkaProducer.queueMessages({ + kafkaMessages: [...kafkaMessages0, ...kafkaMessages1], + waitForAck: true, + }) await processEvent( 'person_1', diff --git a/plugin-server/tests/worker/ingestion/person-state.test.ts b/plugin-server/tests/worker/ingestion/person-state.test.ts index d3a04018e0d96..ab921d71902cc 100644 --- a/plugin-server/tests/worker/ingestion/person-state.test.ts +++ b/plugin-server/tests/worker/ingestion/person-state.test.ts @@ -111,7 +111,6 @@ describe('PersonState.update()', () => { event: Partial, customHub?: Hub, processPerson = true, - lazyPersonCreation = false, timestampParam = timestamp ) { const fullEvent = { @@ -127,7 +126,6 @@ describe('PersonState.update()', () => { timestampParam, processPerson, customHub ? customHub.db : hub.db, - lazyPersonCreation, overridesMode?.getWriter(customHub ?? hub) ) } @@ -164,7 +162,7 @@ describe('PersonState.update()', () => { it('creates deterministic person uuids that are different between teams', async () => { const event_uuid = new UUIDT().toString() const primaryTeamId = teamId - const personPrimaryTeam = await personState({ + const [personPrimaryTeam, kafkaAcks] = await personState({ event: '$pageview', distinct_id: newUserDistinctId, uuid: event_uuid, @@ -172,26 +170,27 @@ describe('PersonState.update()', () => { const otherTeamId = await createTeam(hub.db.postgres, organizationId) teamId = otherTeamId - const personOtherTeam = await personState({ + const [personOtherTeam, kafkaAcksOther] = await personState({ event: '$pageview', distinct_id: newUserDistinctId, uuid: event_uuid, }).updateProperties() await hub.db.kafkaProducer.flush() + await kafkaAcks + await kafkaAcksOther expect(personPrimaryTeam.uuid).toEqual(uuidFromDistinctId(primaryTeamId, newUserDistinctId)) expect(personOtherTeam.uuid).toEqual(uuidFromDistinctId(otherTeamId, newUserDistinctId)) expect(personPrimaryTeam.uuid).not.toEqual(personOtherTeam.uuid) }) - it('returns an ephemeral user object when lazy creation is enabled and $process_person_profile=false', async () => { + it('returns an ephemeral user object when $process_person_profile=false', async () => { const event_uuid = new UUIDT().toString() const hubParam = undefined const processPerson = false - const lazyPersonCreation = true - const fakePerson = await personState( + const [fakePerson, kafkaAcks] = await personState( { event: '$pageview', distinct_id: newUserDistinctId, @@ -199,10 +198,10 @@ describe('PersonState.update()', () => { properties: { $set: { should_be_dropped: 100 } }, }, hubParam, - processPerson, - lazyPersonCreation + processPerson ).update() await hub.db.kafkaProducer.flush() + await kafkaAcks expect(fakePerson).toEqual( expect.objectContaining({ @@ -223,13 +222,12 @@ describe('PersonState.update()', () => { expect(distinctIds).toEqual(expect.arrayContaining([])) }) - it('merging with lazy person creation creates an override and force_upgrade works', async () => { + it('merging creates an override and force_upgrade works', async () => { await hub.db.createPerson(timestamp, {}, {}, {}, teamId, null, false, oldUserUuid, [oldUserDistinctId]) const hubParam = undefined let processPerson = true - const lazyPersonCreation = true - await personState( + const [_person, kafkaAcks] = await personState( { event: '$identify', distinct_id: newUserDistinctId, @@ -238,10 +236,10 @@ describe('PersonState.update()', () => { }, }, hubParam, - processPerson, - lazyPersonCreation + processPerson ).update() await hub.db.kafkaProducer.flush() + await kafkaAcks await delayUntilEventIngested(() => fetchOverridesForDistinctId(newUserDistinctId)) const chOverrides = await fetchOverridesForDistinctId(newUserDistinctId) @@ -263,7 +261,7 @@ describe('PersonState.update()', () => { processPerson = false const event_uuid = new UUIDT().toString() const timestampParam = timestamp.plus({ minutes: 5 }) // Event needs to happen after Person creation - const fakePerson = await personState( + const [fakePerson, kafkaAcks2] = await personState( { event: '$pageview', distinct_id: newUserDistinctId, @@ -272,10 +270,10 @@ describe('PersonState.update()', () => { }, hubParam, processPerson, - lazyPersonCreation, timestampParam ).update() await hub.db.kafkaProducer.flush() + await kafkaAcks2 expect(fakePerson).toEqual( expect.objectContaining({ @@ -290,7 +288,7 @@ describe('PersonState.update()', () => { it('creates person if they are new', async () => { const event_uuid = new UUIDT().toString() - const person = await personState({ + const [person, kafkaAcks] = await personState({ event: '$pageview', distinct_id: newUserDistinctId, uuid: event_uuid, @@ -298,6 +296,7 @@ describe('PersonState.update()', () => { properties: { $set: { null_byte: '\u0000' } }, }).updateProperties() await hub.db.kafkaProducer.flush() + await kafkaAcks expect(person).toEqual( expect.objectContaining({ @@ -323,58 +322,16 @@ describe('PersonState.update()', () => { expect(distinctIds).toEqual(expect.arrayContaining([newUserDistinctId])) }) - it('creates person if they are new and $process_person_profile=false', async () => { - // Note that eventually $process_person_profile=false will be optimized so that the person is - // *not* created here. - const event_uuid = new UUIDT().toString() - const processPerson = false - const person = await personState( - { - event: '$pageview', - distinct_id: newUserDistinctId, - uuid: event_uuid, - properties: { $process_person_profile: false, $set: { a: 1 }, $set_once: { b: 2 } }, - }, - hub, - processPerson - ).update() - await hub.db.kafkaProducer.flush() - - expect(person).toEqual( - expect.objectContaining({ - id: expect.any(Number), - uuid: newUserUuid, - properties: {}, - created_at: timestamp, - version: 0, - is_identified: false, - }) - ) - - expect(hub.db.fetchPerson).toHaveBeenCalledTimes(1) - expect(hub.db.updatePersonDeprecated).not.toHaveBeenCalled() - - // verify Postgres persons - const persons = await fetchPostgresPersonsH() - expect(persons.length).toEqual(1) - // For parity with existing functionality, the Person created in the DB actually gets - // the $creator_event_uuid property. When we stop creating person rows this won't matter. - expect(persons[0]).toEqual({ ...person, properties: { $creator_event_uuid: event_uuid } }) - - // verify Postgres distinct_ids - const distinctIds = await hub.db.fetchDistinctIdValues(persons[0]) - expect(distinctIds).toEqual(expect.arrayContaining([newUserDistinctId])) - }) - it('does not attach existing person properties to $process_person_profile=false events', async () => { const originalEventUuid = new UUIDT().toString() - const person = await personState({ + const [person, kafkaAcks] = await personState({ event: '$pageview', distinct_id: newUserDistinctId, uuid: originalEventUuid, properties: { $set: { c: 420 } }, }).update() await hub.db.kafkaProducer.flush() + await kafkaAcks expect(person).toEqual( expect.objectContaining({ @@ -398,7 +355,7 @@ describe('PersonState.update()', () => { // OK, a person now exists with { c: 420 }, let's prove the properties come back out // of the DB. - const personVerifyProps = await personState({ + const [personVerifyProps] = await personState({ event: '$pageview', distinct_id: newUserDistinctId, uuid: new UUIDT().toString(), @@ -407,7 +364,7 @@ describe('PersonState.update()', () => { expect(personVerifyProps.properties).toEqual({ $creator_event_uuid: originalEventUuid, c: 420 }) // But they don't when $process_person_profile=false - const processPersonFalseResult = await personState( + const [processPersonFalseResult] = await personState( { event: '$pageview', distinct_id: newUserDistinctId, @@ -427,8 +384,12 @@ describe('PersonState.update()', () => { return Promise.resolve(undefined) }) - const person = await personState({ event: '$pageview', distinct_id: newUserDistinctId }).handleUpdate() + const [person, kafkaAcks] = await personState({ + event: '$pageview', + distinct_id: newUserDistinctId, + }).handleUpdate() await hub.db.kafkaProducer.flush() + await kafkaAcks // if creation fails we should return the person that another thread already created expect(person).toEqual( @@ -461,7 +422,7 @@ describe('PersonState.update()', () => { return Promise.resolve(undefined) }) - const person = await personState({ + const [person, kafkaAcks] = await personState({ event: '$pageview', distinct_id: newUserDistinctId, properties: { @@ -470,6 +431,7 @@ describe('PersonState.update()', () => { }, }).handleUpdate() await hub.db.kafkaProducer.flush() + await kafkaAcks // if creation fails we should return the person that another thread already created expect(person).toEqual( @@ -494,7 +456,7 @@ describe('PersonState.update()', () => { }) it('creates person with properties', async () => { - const person = await personState({ + const [person, kafkaAcks] = await personState({ event: '$pageview', distinct_id: newUserDistinctId, properties: { @@ -503,6 +465,7 @@ describe('PersonState.update()', () => { }, }).updateProperties() await hub.db.kafkaProducer.flush() + await kafkaAcks expect(person).toEqual( expect.objectContaining({ @@ -543,7 +506,7 @@ describe('PersonState.update()', () => { [newUserDistinctId] ) - const person = await personState({ + const [person, kafkaAcks] = await personState({ event: '$pageview', distinct_id: newUserDistinctId, properties: { @@ -552,6 +515,7 @@ describe('PersonState.update()', () => { }, }).updateProperties() await hub.db.kafkaProducer.flush() + await kafkaAcks expect(person).toEqual( expect.objectContaining({ @@ -594,9 +558,12 @@ describe('PersonState.update()', () => { $set: { b: 4 }, }, }) - jest.spyOn(personS, 'handleIdentifyOrAlias').mockReturnValue(Promise.resolve(personInitial)) - const person = await personS.update() + jest.spyOn(personS, 'handleIdentifyOrAlias').mockReturnValue( + Promise.resolve([personInitial, Promise.resolve()]) + ) + const [person, kafkaAcks] = await personS.update() await hub.db.kafkaProducer.flush() + await kafkaAcks expect(person).toEqual( expect.objectContaining({ @@ -622,7 +589,7 @@ describe('PersonState.update()', () => { newUserDistinctId, ]) - const person = await personState({ + const [person, kafkaAcks] = await personState({ event: '$pageview', distinct_id: newUserDistinctId, properties: { @@ -631,6 +598,7 @@ describe('PersonState.update()', () => { }, }).updateProperties() await hub.db.kafkaProducer.flush() + await kafkaAcks expect(person).toEqual( expect.objectContaining({ @@ -661,8 +629,9 @@ describe('PersonState.update()', () => { }) personS.updateIsIdentified = true - const person = await personS.updateProperties() + const [person, kafkaAcks] = await personS.updateProperties() await hub.db.kafkaProducer.flush() + await kafkaAcks expect(person).toEqual( expect.objectContaining({ id: expect.any(Number), @@ -713,10 +682,13 @@ describe('PersonState.update()', () => { distinct_id: newUserDistinctId, properties: { $set: { a: 7, d: 9 } }, }) - jest.spyOn(personS, 'handleIdentifyOrAlias').mockReturnValue(Promise.resolve(mergeDeletedPerson)) + jest.spyOn(personS, 'handleIdentifyOrAlias').mockReturnValue( + Promise.resolve([mergeDeletedPerson, Promise.resolve()]) + ) - const person = await personS.update() + const [person, kafkaAcks] = await personS.update() await hub.db.kafkaProducer.flush() + await kafkaAcks expect(person).toEqual( expect.objectContaining({ @@ -746,7 +718,7 @@ describe('PersonState.update()', () => { describe(`overrides: ${useOverridesMode}`, () => { it(`no-op when $anon_distinct_id not passed`, async () => { - const person = await personState({ + const [person, kafkaAcks] = await personState({ event: '$identify', distinct_id: newUserDistinctId, properties: { @@ -754,6 +726,7 @@ describe('PersonState.update()', () => { }, }).handleIdentifyOrAlias() await hub.db.kafkaProducer.flush() + await kafkaAcks expect(person).toEqual(undefined) const persons = await fetchPostgresPersonsH() @@ -761,7 +734,7 @@ describe('PersonState.update()', () => { }) it(`creates person with both distinct_ids and marks user as is_identified when $anon_distinct_id passed`, async () => { - const person = await personState({ + const [person, kafkaAcks] = await personState({ event: '$identify', distinct_id: newUserDistinctId, properties: { @@ -770,6 +743,7 @@ describe('PersonState.update()', () => { }, }).handleIdentifyOrAlias() await hub.db.kafkaProducer.flush() + await kafkaAcks expect(person).toEqual( expect.objectContaining({ @@ -777,7 +751,7 @@ describe('PersonState.update()', () => { uuid: newUserUuid, properties: { foo: 'bar' }, created_at: timestamp, - version: 0, + version: 1, is_identified: true, }) ) @@ -807,8 +781,9 @@ describe('PersonState.update()', () => { $anon_distinct_id: oldUserDistinctId, }, }) - const person = await personS.handleIdentifyOrAlias() + const [person, kafkaAcks] = await personS.handleIdentifyOrAlias() await hub.db.kafkaProducer.flush() + await kafkaAcks expect(person).toEqual( expect.objectContaining({ @@ -838,8 +813,9 @@ describe('PersonState.update()', () => { $anon_distinct_id: oldUserDistinctId, }, }) - const person = await personS.handleIdentifyOrAlias() + const [person, kafkaAcks] = await personS.handleIdentifyOrAlias() await hub.db.kafkaProducer.flush() + await kafkaAcks const persons = await fetchPostgresPersonsH() expect(person).toEqual( @@ -873,8 +849,9 @@ describe('PersonState.update()', () => { $anon_distinct_id: oldUserDistinctId, }, }) - const person = await personS.handleIdentifyOrAlias() + const [person, kafkaAcks] = await personS.handleIdentifyOrAlias() await hub.db.kafkaProducer.flush() + await kafkaAcks const persons = await fetchPostgresPersonsH() @@ -903,7 +880,7 @@ describe('PersonState.update()', () => { await hub.db.createPerson(timestamp, {}, {}, {}, teamId, null, false, oldUserUuid, [oldUserDistinctId]) await hub.db.createPerson(timestamp2, {}, {}, {}, teamId, null, false, newUserUuid, [newUserDistinctId]) - const person = await personState({ + const [person, kafkaAcks] = await personState({ event: '$identify', distinct_id: newUserDistinctId, properties: { @@ -911,6 +888,7 @@ describe('PersonState.update()', () => { }, }).handleIdentifyOrAlias() await hub.db.kafkaProducer.flush() + await kafkaAcks expect(person).toEqual( expect.objectContaining({ @@ -965,7 +943,7 @@ describe('PersonState.update()', () => { await hub.db.createPerson(timestamp, {}, {}, {}, teamId, null, false, oldUserUuid, [oldUserDistinctId]) await hub.db.createPerson(timestamp2, {}, {}, {}, teamId, null, true, newUserUuid, [newUserDistinctId]) - const person = await personState({ + const [person, kafkaAcks] = await personState({ event: '$identify', distinct_id: newUserDistinctId, properties: { @@ -973,6 +951,7 @@ describe('PersonState.update()', () => { }, }).handleIdentifyOrAlias() await hub.db.kafkaProducer.flush() + await kafkaAcks expect(person).toEqual( expect.objectContaining({ @@ -1034,8 +1013,9 @@ describe('PersonState.update()', () => { $anon_distinct_id: oldUserDistinctId, }, }) - const person = await personS.handleIdentifyOrAlias() + const [person, kafkaAcks] = await personS.handleIdentifyOrAlias() await hub.db.kafkaProducer.flush() + await kafkaAcks expect(personS.updateIsIdentified).toBeTruthy() expect(person).toEqual( @@ -1075,7 +1055,7 @@ describe('PersonState.update()', () => { await hub.db.createPerson(timestamp, {}, {}, {}, teamId, null, true, oldUserUuid, [oldUserDistinctId]) await hub.db.createPerson(timestamp2, {}, {}, {}, teamId, null, true, newUserUuid, [newUserDistinctId]) - const person = await personState({ + const [person, kafkaAcks] = await personState({ event: '$identify', distinct_id: newUserDistinctId, properties: { @@ -1083,6 +1063,7 @@ describe('PersonState.update()', () => { }, }).handleIdentifyOrAlias() await hub.db.kafkaProducer.flush() + await kafkaAcks expect(person).toEqual( expect.objectContaining({ @@ -1125,7 +1106,7 @@ describe('PersonState.update()', () => { newUserDistinctId, ]) - const person = await personState({ + const [person, kafkaAcks] = await personState({ event: '$identify', distinct_id: newUserDistinctId, properties: { @@ -1135,6 +1116,7 @@ describe('PersonState.update()', () => { }, }).handleIdentifyOrAlias() await hub.db.kafkaProducer.flush() + await kafkaAcks expect(person).toEqual( expect.objectContaining({ @@ -1204,7 +1186,7 @@ describe('PersonState.update()', () => { await hub.db.addDistinctId(person, distinctId, 0) // this throws }) - const person = await personState({ + const [person, kafkaAcks] = await personState({ event: '$identify', distinct_id: oldUserDistinctId, properties: { @@ -1212,6 +1194,7 @@ describe('PersonState.update()', () => { }, }).handleIdentifyOrAlias() await hub.db.kafkaProducer.flush() + await kafkaAcks jest.spyOn(hub.db, 'addDistinctId').mockRestore() // Necessary for other tests not to fail // if creation fails we should return the person that another thread already created @@ -1250,7 +1233,7 @@ describe('PersonState.update()', () => { hub ) jest.spyOn(state, 'merge').mockImplementation(() => { - return Promise.resolve(undefined) + return Promise.resolve([undefined, Promise.resolve()]) }) await state.handleIdentifyOrAlias() expect(state.merge).toHaveBeenCalledWith(oldUserDistinctId, newUserDistinctId, teamId, timestamp) @@ -1267,7 +1250,7 @@ describe('PersonState.update()', () => { hub ) jest.spyOn(state, 'merge').mockImplementation(() => { - return Promise.resolve(undefined) + return Promise.resolve([undefined, Promise.resolve()]) }) await state.handleIdentifyOrAlias() @@ -1285,7 +1268,7 @@ describe('PersonState.update()', () => { hub ) jest.spyOn(state, 'merge').mockImplementation(() => { - return Promise.resolve(undefined) + return Promise.resolve([undefined, Promise.resolve()]) }) await state.handleIdentifyOrAlias() @@ -1305,7 +1288,7 @@ describe('PersonState.update()', () => { await hub.db.createPerson(timestamp, {}, {}, {}, teamId, null, true, oldUserUuid, [oldUserDistinctId]) await hub.db.createPerson(timestamp2, {}, {}, {}, teamId, null, true, newUserUuid, [newUserDistinctId]) - const person = await personState({ + const [person, kafkaAcks] = await personState({ event: '$merge_dangerously', distinct_id: newUserDistinctId, properties: { @@ -1313,6 +1296,7 @@ describe('PersonState.update()', () => { }, }).handleIdentifyOrAlias() await hub.db.kafkaProducer.flush() + await kafkaAcks expect(person).toEqual( expect.objectContaining({ @@ -1368,7 +1352,7 @@ describe('PersonState.update()', () => { describe('illegal aliasing', () => { const illegalIds = ['', ' ', 'null', 'undefined', '"undefined"', '[object Object]', '"[object Object]"'] it.each(illegalIds)('stops $identify if current distinct_id is illegal: `%s`', async (illegalId: string) => { - const person = await personState({ + const [person] = await personState({ event: '$identify', distinct_id: illegalId, properties: { @@ -1382,7 +1366,7 @@ describe('PersonState.update()', () => { }) it.each(illegalIds)('stops $identify if $anon_distinct_id is illegal: `%s`', async (illegalId: string) => { - const person = await personState({ + const [person] = await personState({ event: '$identify', distinct_id: 'some_distinct_id', properties: { @@ -1396,7 +1380,7 @@ describe('PersonState.update()', () => { }) it('stops $create_alias if current distinct_id is illegal', async () => { - const person = await personState({ + const [person] = await personState({ event: '$create_alias', distinct_id: 'false', properties: { @@ -1410,7 +1394,7 @@ describe('PersonState.update()', () => { }) it('stops $create_alias if alias is illegal', async () => { - const person = await personState({ + const [person] = await personState({ event: '$create_alias', distinct_id: 'some_distinct_id', properties: { @@ -1685,8 +1669,14 @@ describe('PersonState.update()', () => { ]) const state: PersonState = personState({}, hub) jest.spyOn(hub.db.kafkaProducer, 'queueMessages') - const person = await state.merge(secondUserDistinctId, firstUserDistinctId, teamId, timestamp) + const [person, kafkaAcks] = await state.merge( + secondUserDistinctId, + firstUserDistinctId, + teamId, + timestamp + ) await hub.db.kafkaProducer.flush() + await kafkaAcks expect(person).toEqual( expect.objectContaining({ @@ -1728,13 +1718,14 @@ describe('PersonState.update()', () => { const state: PersonState = personState({}, hub) jest.spyOn(hub.db.kafkaProducer, 'queueMessages') - const person = await state.mergePeople({ + const [person, kafkaAcks] = await state.mergePeople({ mergeInto: first, mergeIntoDistinctId: firstUserDistinctId, otherPerson: second, otherPersonDistinctId: secondUserDistinctId, }) await hub.db.kafkaProducer.flush() + await kafkaAcks expect(person).toEqual( expect.objectContaining({ @@ -2060,13 +2051,14 @@ describe('PersonState.update()', () => { // Now verify we successfully get to our target state if we do not have // any db errors. mockPostgresQuery.mockRestore() - const person = await state.mergePeople({ + const [person, kafkaAcks] = await state.mergePeople({ mergeInto: first, mergeIntoDistinctId: firstUserDistinctId, otherPerson: second, otherPersonDistinctId: secondUserDistinctId, }) await hub.db.kafkaProducer.flush() + await kafkaAcks expect(person).toEqual( expect.objectContaining({ diff --git a/plugin-server/tests/worker/ingestion/postgres-parity.test.ts b/plugin-server/tests/worker/ingestion/postgres-parity.test.ts index 142b7c6938bd6..5b12393a63b10 100644 --- a/plugin-server/tests/worker/ingestion/postgres-parity.test.ts +++ b/plugin-server/tests/worker/ingestion/postgres-parity.test.ts @@ -170,11 +170,16 @@ describe('postgres parity', () => { await delayUntilEventIngested(() => hub.db.fetchDistinctIdValues(person, Database.ClickHouse), 2) // update properties and set is_identified to true - await hub.db.updatePersonDeprecated(person, { + const [_p, kafkaMessages] = await hub.db.updatePersonDeprecated(person, { properties: { replacedUserProp: 'propValue' }, is_identified: true, }) + await hub.db.kafkaProducer.queueMessages({ + kafkaMessages, + waitForAck: true, + }) + await delayUntilEventIngested(async () => (await hub.db.fetchPersons(Database.ClickHouse)).filter((p) => p.is_identified) ) @@ -196,11 +201,16 @@ describe('postgres parity', () => { // update date and boolean to false const randomDate = DateTime.utc().minus(100000).setZone('UTC') - const [updatedPerson] = await hub.db.updatePersonDeprecated(person, { + const [updatedPerson, kafkaMessages2] = await hub.db.updatePersonDeprecated(person, { created_at: randomDate, is_identified: false, }) + await hub.db.kafkaProducer.queueMessages({ + kafkaMessages: kafkaMessages2, + waitForAck: true, + }) + expect(updatedPerson.version).toEqual(2) await delayUntilEventIngested(async () => diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4789afdebaff4..6c9eab81c8a89 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -260,8 +260,8 @@ dependencies: specifier: ^9.3.0 version: 9.3.0(postcss@8.4.31) posthog-js: - specifier: 1.138.2 - version: 1.138.2 + specifier: 1.139.0 + version: 1.139.0 posthog-js-lite: specifier: 3.0.0 version: 3.0.0 @@ -17707,8 +17707,8 @@ packages: resolution: {integrity: sha512-dyajjnfzZD1tht4N7p7iwf7nBnR1MjVaVu+MKr+7gBgA39bn28wizCIJZztZPtHy4PY0YwtSGgwfBCuG/hnHgA==} dev: false - /posthog-js@1.138.2: - resolution: {integrity: sha512-siin1JCAe8UIrc39qV5SFwxBcUB7zp80KNKp175McMGh3Vtw056AccFTBw6xpuIjX5hh23gfw7Pnr/VnI7MSfw==} + /posthog-js@1.139.0: + resolution: {integrity: sha512-FuYlxQFO0Dq5X1/bFEM8F+NgOqZiVh4fPVHHeOTWMkqVP+pCnODQitbtW0hgT0/EE665w0xpZBk93YavaZRhzQ==} dependencies: fflate: 0.4.8 preact: 10.22.0 diff --git a/posthog/api/__init__.py b/posthog/api/__init__.py index 9e1f2d8458886..dbe3d1bd17515 100644 --- a/posthog/api/__init__.py +++ b/posthog/api/__init__.py @@ -20,6 +20,7 @@ event_definition, exports, feature_flag, + hog_function, ingestion_warnings, instance_settings, instance_status, @@ -408,6 +409,13 @@ def api_not_found(request): ["team_id"], ) +projects_router.register( + r"hog_functions", + hog_function.HogFunctionViewSet, + "project_hog_functions", + ["team_id"], +) + projects_router.register( r"alerts", alert.AlertViewSet, diff --git a/posthog/api/app_metrics.py b/posthog/api/app_metrics.py index 8f97638cd0fbf..61612980e24f5 100644 --- a/posthog/api/app_metrics.py +++ b/posthog/api/app_metrics.py @@ -56,23 +56,30 @@ def retrieve(self, request: request.Request, *args: Any, **kwargs: Any) -> respo except ValueError: pass - plugin_config = self.get_object() - filter = AppMetricsRequestSerializer(data=request.query_params) filter.is_valid(raise_exception=True) - metric_results = AppMetricsQuery(self.team, plugin_config.pk, filter).run() - errors = AppMetricsErrorsQuery(self.team, plugin_config.pk, filter).run() + if "hog-" in kwargs["pk"]: + # TODO: Make app metrics work with string IDs + metric_results = { + "dates": [], + "successes": [], + "successes_on_retry": [], + "failures": [], + "totals": {"successes": 0, "successes_on_retry": 0, "failures": 0}, + } + errors = [] + else: + metric_results = AppMetricsQuery(self.team, kwargs["pk"], filter).run() + errors = AppMetricsErrorsQuery(self.team, kwargs["pk"], filter).run() return response.Response({"metrics": metric_results, "errors": errors}) @action(methods=["GET"], detail=True) def error_details(self, request: request.Request, *args: Any, **kwargs: Any) -> response.Response: - plugin_config = self.get_object() - filter = AppMetricsErrorsRequestSerializer(data=request.query_params) filter.is_valid(raise_exception=True) - error_details = AppMetricsErrorDetailsQuery(self.team, plugin_config.pk, filter).run() + error_details = AppMetricsErrorDetailsQuery(self.team, kwargs["pk"], filter).run() return response.Response({"result": error_details}) def get_batch_export_runs_app_metrics_queryset(self, batch_export_id: str): diff --git a/posthog/api/team.py b/posthog/api/team.py index bb3395a5c56ea..e96ab0820eb55 100644 --- a/posthog/api/team.py +++ b/posthog/api/team.py @@ -199,9 +199,9 @@ def get_groups_on_events_querying_enabled(self, team: Team) -> bool: def get_live_events_token(self, team: Team) -> Optional[str]: return encode_jwt( - {"team_id": 2}, + {"team_id": team.id}, timedelta(days=7), - PosthogJwtAudience.LIVE_EVENTS, + PosthogJwtAudience.LIVESTREAM, ) def validate_session_recording_linked_flag(self, value) -> dict | None: diff --git a/posthog/api/test/__snapshots__/test_decide.ambr b/posthog/api/test/__snapshots__/test_decide.ambr index eb0091a95ba5c..d03538893572b 100644 --- a/posthog/api/test/__snapshots__/test_decide.ambr +++ b/posthog/api/test/__snapshots__/test_decide.ambr @@ -66,6 +66,29 @@ ''' # --- # name: TestDecide.test_decide_doesnt_error_out_when_database_is_down.10 + ''' + SELECT "posthog_featureflag"."id", + "posthog_featureflag"."key", + "posthog_featureflag"."name", + "posthog_featureflag"."filters", + "posthog_featureflag"."rollout_percentage", + "posthog_featureflag"."team_id", + "posthog_featureflag"."created_by_id", + "posthog_featureflag"."created_at", + "posthog_featureflag"."deleted", + "posthog_featureflag"."active", + "posthog_featureflag"."rollback_conditions", + "posthog_featureflag"."performed_rollback", + "posthog_featureflag"."ensure_experience_continuity", + "posthog_featureflag"."usage_dashboard_id", + "posthog_featureflag"."has_enriched_analytics" + FROM "posthog_featureflag" + WHERE ("posthog_featureflag"."active" + AND NOT "posthog_featureflag"."deleted" + AND "posthog_featureflag"."team_id" = 2) + ''' +# --- +# name: TestDecide.test_decide_doesnt_error_out_when_database_is_down.11 ''' SELECT "posthog_pluginconfig"."id", "posthog_pluginconfig"."web_token", @@ -81,17 +104,6 @@ AND "posthog_pluginconfig"."team_id" = 2) ''' # --- -# name: TestDecide.test_decide_doesnt_error_out_when_database_is_down.11 - ''' - SELECT "posthog_instancesetting"."id", - "posthog_instancesetting"."key", - "posthog_instancesetting"."raw_value" - FROM "posthog_instancesetting" - WHERE "posthog_instancesetting"."key" = 'constance:posthog:PERSON_ON_EVENTS_V2_ENABLED' - ORDER BY "posthog_instancesetting"."id" ASC - LIMIT 1 - ''' -# --- # name: TestDecide.test_decide_doesnt_error_out_when_database_is_down.12 ''' SELECT "posthog_instancesetting"."id", @@ -316,6 +328,83 @@ ''' # --- # name: TestDecide.test_decide_doesnt_error_out_when_database_is_down.7 + ''' + SELECT "posthog_hogfunction"."id", + "posthog_hogfunction"."team_id", + "posthog_hogfunction"."name", + "posthog_hogfunction"."description", + "posthog_hogfunction"."created_at", + "posthog_hogfunction"."created_by_id", + "posthog_hogfunction"."deleted", + "posthog_hogfunction"."updated_at", + "posthog_hogfunction"."enabled", + "posthog_hogfunction"."hog", + "posthog_hogfunction"."bytecode", + "posthog_hogfunction"."inputs_schema", + "posthog_hogfunction"."inputs", + "posthog_hogfunction"."filters", + "posthog_team"."id", + "posthog_team"."uuid", + "posthog_team"."organization_id", + "posthog_team"."project_id", + "posthog_team"."api_token", + "posthog_team"."app_urls", + "posthog_team"."name", + "posthog_team"."slack_incoming_webhook", + "posthog_team"."created_at", + "posthog_team"."updated_at", + "posthog_team"."anonymize_ips", + "posthog_team"."completed_snippet_onboarding", + "posthog_team"."has_completed_onboarding_for", + "posthog_team"."ingested_event", + "posthog_team"."autocapture_opt_out", + "posthog_team"."autocapture_exceptions_opt_in", + "posthog_team"."autocapture_exceptions_errors_to_ignore", + "posthog_team"."session_recording_opt_in", + "posthog_team"."session_recording_sample_rate", + "posthog_team"."session_recording_minimum_duration_milliseconds", + "posthog_team"."session_recording_linked_flag", + "posthog_team"."session_recording_network_payload_capture_config", + "posthog_team"."session_replay_config", + "posthog_team"."capture_console_log_opt_in", + "posthog_team"."capture_performance_opt_in", + "posthog_team"."surveys_opt_in", + "posthog_team"."heatmaps_opt_in", + "posthog_team"."session_recording_version", + "posthog_team"."signup_token", + "posthog_team"."is_demo", + "posthog_team"."access_control", + "posthog_team"."week_start_day", + "posthog_team"."inject_web_apps", + "posthog_team"."test_account_filters", + "posthog_team"."test_account_filters_default_checked", + "posthog_team"."path_cleaning_filters", + "posthog_team"."timezone", + "posthog_team"."data_attributes", + "posthog_team"."person_display_name_properties", + "posthog_team"."live_events_columns", + "posthog_team"."recording_domains", + "posthog_team"."primary_dashboard_id", + "posthog_team"."extra_settings", + "posthog_team"."modifiers", + "posthog_team"."correlation_config", + "posthog_team"."session_recording_retention_period_days", + "posthog_team"."plugins_opt_in", + "posthog_team"."opt_out_capture", + "posthog_team"."event_names", + "posthog_team"."event_names_with_usage", + "posthog_team"."event_properties", + "posthog_team"."event_properties_with_usage", + "posthog_team"."event_properties_numerical", + "posthog_team"."external_data_workspace_id", + "posthog_team"."external_data_workspace_last_synced_at" + FROM "posthog_hogfunction" + INNER JOIN "posthog_team" ON ("posthog_hogfunction"."team_id" = "posthog_team"."id") + WHERE ("posthog_hogfunction"."team_id" = 2 + AND "posthog_hogfunction"."filters" @> '{"filter_test_accounts": true}'::jsonb) + ''' +# --- +# name: TestDecide.test_decide_doesnt_error_out_when_database_is_down.8 ''' SELECT 1 AS "a" FROM "posthog_grouptypemapping" @@ -323,7 +412,7 @@ LIMIT 1 ''' # --- -# name: TestDecide.test_decide_doesnt_error_out_when_database_is_down.8 +# name: TestDecide.test_decide_doesnt_error_out_when_database_is_down.9 ''' SELECT "posthog_user"."id", "posthog_user"."password", @@ -355,30 +444,84 @@ LIMIT 21 ''' # --- -# name: TestDecide.test_decide_doesnt_error_out_when_database_is_down.9 +# name: TestDecide.test_flag_with_behavioural_cohorts ''' - SELECT "posthog_featureflag"."id", - "posthog_featureflag"."key", - "posthog_featureflag"."name", - "posthog_featureflag"."filters", - "posthog_featureflag"."rollout_percentage", - "posthog_featureflag"."team_id", - "posthog_featureflag"."created_by_id", - "posthog_featureflag"."created_at", - "posthog_featureflag"."deleted", - "posthog_featureflag"."active", - "posthog_featureflag"."rollback_conditions", - "posthog_featureflag"."performed_rollback", - "posthog_featureflag"."ensure_experience_continuity", - "posthog_featureflag"."usage_dashboard_id", - "posthog_featureflag"."has_enriched_analytics" - FROM "posthog_featureflag" - WHERE ("posthog_featureflag"."active" - AND NOT "posthog_featureflag"."deleted" - AND "posthog_featureflag"."team_id" = 2) + SELECT "posthog_hogfunction"."id", + "posthog_hogfunction"."team_id", + "posthog_hogfunction"."name", + "posthog_hogfunction"."description", + "posthog_hogfunction"."created_at", + "posthog_hogfunction"."created_by_id", + "posthog_hogfunction"."deleted", + "posthog_hogfunction"."updated_at", + "posthog_hogfunction"."enabled", + "posthog_hogfunction"."hog", + "posthog_hogfunction"."bytecode", + "posthog_hogfunction"."inputs_schema", + "posthog_hogfunction"."inputs", + "posthog_hogfunction"."filters", + "posthog_team"."id", + "posthog_team"."uuid", + "posthog_team"."organization_id", + "posthog_team"."project_id", + "posthog_team"."api_token", + "posthog_team"."app_urls", + "posthog_team"."name", + "posthog_team"."slack_incoming_webhook", + "posthog_team"."created_at", + "posthog_team"."updated_at", + "posthog_team"."anonymize_ips", + "posthog_team"."completed_snippet_onboarding", + "posthog_team"."has_completed_onboarding_for", + "posthog_team"."ingested_event", + "posthog_team"."autocapture_opt_out", + "posthog_team"."autocapture_exceptions_opt_in", + "posthog_team"."autocapture_exceptions_errors_to_ignore", + "posthog_team"."session_recording_opt_in", + "posthog_team"."session_recording_sample_rate", + "posthog_team"."session_recording_minimum_duration_milliseconds", + "posthog_team"."session_recording_linked_flag", + "posthog_team"."session_recording_network_payload_capture_config", + "posthog_team"."session_replay_config", + "posthog_team"."capture_console_log_opt_in", + "posthog_team"."capture_performance_opt_in", + "posthog_team"."surveys_opt_in", + "posthog_team"."heatmaps_opt_in", + "posthog_team"."session_recording_version", + "posthog_team"."signup_token", + "posthog_team"."is_demo", + "posthog_team"."access_control", + "posthog_team"."week_start_day", + "posthog_team"."inject_web_apps", + "posthog_team"."test_account_filters", + "posthog_team"."test_account_filters_default_checked", + "posthog_team"."path_cleaning_filters", + "posthog_team"."timezone", + "posthog_team"."data_attributes", + "posthog_team"."person_display_name_properties", + "posthog_team"."live_events_columns", + "posthog_team"."recording_domains", + "posthog_team"."primary_dashboard_id", + "posthog_team"."extra_settings", + "posthog_team"."modifiers", + "posthog_team"."correlation_config", + "posthog_team"."session_recording_retention_period_days", + "posthog_team"."plugins_opt_in", + "posthog_team"."opt_out_capture", + "posthog_team"."event_names", + "posthog_team"."event_names_with_usage", + "posthog_team"."event_properties", + "posthog_team"."event_properties_with_usage", + "posthog_team"."event_properties_numerical", + "posthog_team"."external_data_workspace_id", + "posthog_team"."external_data_workspace_last_synced_at" + FROM "posthog_hogfunction" + INNER JOIN "posthog_team" ON ("posthog_hogfunction"."team_id" = "posthog_team"."id") + WHERE ("posthog_hogfunction"."team_id" = 2 + AND "posthog_hogfunction"."filters" @> '{"filter_test_accounts": true}'::jsonb) ''' # --- -# name: TestDecide.test_flag_with_behavioural_cohorts +# name: TestDecide.test_flag_with_behavioural_cohorts.1 ''' SELECT "posthog_user"."id", "posthog_user"."password", @@ -410,7 +553,7 @@ LIMIT 21 ''' # --- -# name: TestDecide.test_flag_with_behavioural_cohorts.1 +# name: TestDecide.test_flag_with_behavioural_cohorts.2 ''' SELECT "posthog_team"."id", "posthog_team"."uuid", @@ -472,7 +615,7 @@ LIMIT 21 ''' # --- -# name: TestDecide.test_flag_with_behavioural_cohorts.2 +# name: TestDecide.test_flag_with_behavioural_cohorts.3 ''' SELECT "posthog_featureflag"."id", "posthog_featureflag"."key", @@ -495,7 +638,7 @@ AND "posthog_featureflag"."team_id" = 2) ''' # --- -# name: TestDecide.test_flag_with_behavioural_cohorts.3 +# name: TestDecide.test_flag_with_behavioural_cohorts.4 ''' SELECT "posthog_cohort"."id", "posthog_cohort"."name", @@ -519,7 +662,7 @@ AND "posthog_cohort"."team_id" = 2) ''' # --- -# name: TestDecide.test_flag_with_behavioural_cohorts.4 +# name: TestDecide.test_flag_with_behavioural_cohorts.5 ''' SELECT "posthog_cohort"."id", "posthog_cohort"."name", @@ -544,6 +687,83 @@ ''' # --- # name: TestDecide.test_flag_with_regular_cohorts + ''' + SELECT "posthog_hogfunction"."id", + "posthog_hogfunction"."team_id", + "posthog_hogfunction"."name", + "posthog_hogfunction"."description", + "posthog_hogfunction"."created_at", + "posthog_hogfunction"."created_by_id", + "posthog_hogfunction"."deleted", + "posthog_hogfunction"."updated_at", + "posthog_hogfunction"."enabled", + "posthog_hogfunction"."hog", + "posthog_hogfunction"."bytecode", + "posthog_hogfunction"."inputs_schema", + "posthog_hogfunction"."inputs", + "posthog_hogfunction"."filters", + "posthog_team"."id", + "posthog_team"."uuid", + "posthog_team"."organization_id", + "posthog_team"."project_id", + "posthog_team"."api_token", + "posthog_team"."app_urls", + "posthog_team"."name", + "posthog_team"."slack_incoming_webhook", + "posthog_team"."created_at", + "posthog_team"."updated_at", + "posthog_team"."anonymize_ips", + "posthog_team"."completed_snippet_onboarding", + "posthog_team"."has_completed_onboarding_for", + "posthog_team"."ingested_event", + "posthog_team"."autocapture_opt_out", + "posthog_team"."autocapture_exceptions_opt_in", + "posthog_team"."autocapture_exceptions_errors_to_ignore", + "posthog_team"."session_recording_opt_in", + "posthog_team"."session_recording_sample_rate", + "posthog_team"."session_recording_minimum_duration_milliseconds", + "posthog_team"."session_recording_linked_flag", + "posthog_team"."session_recording_network_payload_capture_config", + "posthog_team"."session_replay_config", + "posthog_team"."capture_console_log_opt_in", + "posthog_team"."capture_performance_opt_in", + "posthog_team"."surveys_opt_in", + "posthog_team"."heatmaps_opt_in", + "posthog_team"."session_recording_version", + "posthog_team"."signup_token", + "posthog_team"."is_demo", + "posthog_team"."access_control", + "posthog_team"."week_start_day", + "posthog_team"."inject_web_apps", + "posthog_team"."test_account_filters", + "posthog_team"."test_account_filters_default_checked", + "posthog_team"."path_cleaning_filters", + "posthog_team"."timezone", + "posthog_team"."data_attributes", + "posthog_team"."person_display_name_properties", + "posthog_team"."live_events_columns", + "posthog_team"."recording_domains", + "posthog_team"."primary_dashboard_id", + "posthog_team"."extra_settings", + "posthog_team"."modifiers", + "posthog_team"."correlation_config", + "posthog_team"."session_recording_retention_period_days", + "posthog_team"."plugins_opt_in", + "posthog_team"."opt_out_capture", + "posthog_team"."event_names", + "posthog_team"."event_names_with_usage", + "posthog_team"."event_properties", + "posthog_team"."event_properties_with_usage", + "posthog_team"."event_properties_numerical", + "posthog_team"."external_data_workspace_id", + "posthog_team"."external_data_workspace_last_synced_at" + FROM "posthog_hogfunction" + INNER JOIN "posthog_team" ON ("posthog_hogfunction"."team_id" = "posthog_team"."id") + WHERE ("posthog_hogfunction"."team_id" = 2 + AND "posthog_hogfunction"."filters" @> '{"filter_test_accounts": true}'::jsonb) + ''' +# --- +# name: TestDecide.test_flag_with_regular_cohorts.1 ''' SELECT "posthog_user"."id", "posthog_user"."password", @@ -575,7 +795,7 @@ LIMIT 21 ''' # --- -# name: TestDecide.test_flag_with_regular_cohorts.1 +# name: TestDecide.test_flag_with_regular_cohorts.2 ''' SELECT "posthog_team"."id", "posthog_team"."uuid", @@ -637,7 +857,7 @@ LIMIT 21 ''' # --- -# name: TestDecide.test_flag_with_regular_cohorts.2 +# name: TestDecide.test_flag_with_regular_cohorts.3 ''' SELECT "posthog_featureflag"."id", "posthog_featureflag"."key", @@ -660,7 +880,7 @@ AND "posthog_featureflag"."team_id" = 2) ''' # --- -# name: TestDecide.test_flag_with_regular_cohorts.3 +# name: TestDecide.test_flag_with_regular_cohorts.4 ''' SELECT "posthog_cohort"."id", "posthog_cohort"."name", @@ -684,7 +904,7 @@ AND "posthog_cohort"."team_id" = 2) ''' # --- -# name: TestDecide.test_flag_with_regular_cohorts.4 +# name: TestDecide.test_flag_with_regular_cohorts.5 ''' SELECT (("posthog_person"."properties" -> '$some_prop_1') = '"something_1"'::jsonb AND "posthog_person"."properties" ? '$some_prop_1' @@ -696,7 +916,7 @@ AND "posthog_person"."team_id" = 2) ''' # --- -# name: TestDecide.test_flag_with_regular_cohorts.5 +# name: TestDecide.test_flag_with_regular_cohorts.6 ''' SELECT "posthog_cohort"."id", "posthog_cohort"."name", @@ -720,7 +940,7 @@ AND "posthog_cohort"."team_id" = 2) ''' # --- -# name: TestDecide.test_flag_with_regular_cohorts.6 +# name: TestDecide.test_flag_with_regular_cohorts.7 ''' SELECT (("posthog_person"."properties" -> '$some_prop_1') = '"something_1"'::jsonb AND "posthog_person"."properties" ? '$some_prop_1' @@ -827,6 +1047,83 @@ ''' # --- # name: TestDecide.test_web_app_queries.3 + ''' + SELECT "posthog_hogfunction"."id", + "posthog_hogfunction"."team_id", + "posthog_hogfunction"."name", + "posthog_hogfunction"."description", + "posthog_hogfunction"."created_at", + "posthog_hogfunction"."created_by_id", + "posthog_hogfunction"."deleted", + "posthog_hogfunction"."updated_at", + "posthog_hogfunction"."enabled", + "posthog_hogfunction"."hog", + "posthog_hogfunction"."bytecode", + "posthog_hogfunction"."inputs_schema", + "posthog_hogfunction"."inputs", + "posthog_hogfunction"."filters", + "posthog_team"."id", + "posthog_team"."uuid", + "posthog_team"."organization_id", + "posthog_team"."project_id", + "posthog_team"."api_token", + "posthog_team"."app_urls", + "posthog_team"."name", + "posthog_team"."slack_incoming_webhook", + "posthog_team"."created_at", + "posthog_team"."updated_at", + "posthog_team"."anonymize_ips", + "posthog_team"."completed_snippet_onboarding", + "posthog_team"."has_completed_onboarding_for", + "posthog_team"."ingested_event", + "posthog_team"."autocapture_opt_out", + "posthog_team"."autocapture_exceptions_opt_in", + "posthog_team"."autocapture_exceptions_errors_to_ignore", + "posthog_team"."session_recording_opt_in", + "posthog_team"."session_recording_sample_rate", + "posthog_team"."session_recording_minimum_duration_milliseconds", + "posthog_team"."session_recording_linked_flag", + "posthog_team"."session_recording_network_payload_capture_config", + "posthog_team"."session_replay_config", + "posthog_team"."capture_console_log_opt_in", + "posthog_team"."capture_performance_opt_in", + "posthog_team"."surveys_opt_in", + "posthog_team"."heatmaps_opt_in", + "posthog_team"."session_recording_version", + "posthog_team"."signup_token", + "posthog_team"."is_demo", + "posthog_team"."access_control", + "posthog_team"."week_start_day", + "posthog_team"."inject_web_apps", + "posthog_team"."test_account_filters", + "posthog_team"."test_account_filters_default_checked", + "posthog_team"."path_cleaning_filters", + "posthog_team"."timezone", + "posthog_team"."data_attributes", + "posthog_team"."person_display_name_properties", + "posthog_team"."live_events_columns", + "posthog_team"."recording_domains", + "posthog_team"."primary_dashboard_id", + "posthog_team"."extra_settings", + "posthog_team"."modifiers", + "posthog_team"."correlation_config", + "posthog_team"."session_recording_retention_period_days", + "posthog_team"."plugins_opt_in", + "posthog_team"."opt_out_capture", + "posthog_team"."event_names", + "posthog_team"."event_names_with_usage", + "posthog_team"."event_properties", + "posthog_team"."event_properties_with_usage", + "posthog_team"."event_properties_numerical", + "posthog_team"."external_data_workspace_id", + "posthog_team"."external_data_workspace_last_synced_at" + FROM "posthog_hogfunction" + INNER JOIN "posthog_team" ON ("posthog_hogfunction"."team_id" = "posthog_team"."id") + WHERE ("posthog_hogfunction"."team_id" = 2 + AND "posthog_hogfunction"."filters" @> '{"filter_test_accounts": true}'::jsonb) + ''' +# --- +# name: TestDecide.test_web_app_queries.4 ''' SELECT "posthog_pluginconfig"."id", "posthog_pluginconfig"."web_token", @@ -842,7 +1139,7 @@ AND "posthog_pluginconfig"."team_id" = 2) ''' # --- -# name: TestDecide.test_web_app_queries.4 +# name: TestDecide.test_web_app_queries.5 ''' SELECT "posthog_pluginconfig"."id", "posthog_pluginconfig"."web_token", diff --git a/posthog/api/test/test_cohort.py b/posthog/api/test/test_cohort.py index 1876463862584..82740d73ed515 100644 --- a/posthog/api/test/test_cohort.py +++ b/posthog/api/test/test_cohort.py @@ -816,7 +816,7 @@ def test_creating_update_and_calculating_with_new_cohort_query(self, patch_captu "key": "$some_prop", "value": "something", "type": "person", - "operator": PropertyOperator.exact, + "operator": PropertyOperator.EXACT, } ], }, @@ -846,7 +846,7 @@ def test_creating_update_and_calculating_with_new_cohort_query_dynamic_error(sel "key": "$some_prop", "value": "something", "type": "person", - "operator": PropertyOperator.exact, + "operator": PropertyOperator.EXACT, } ], }, diff --git a/posthog/api/test/test_insight.py b/posthog/api/test/test_insight.py index 64d4cd7791d38..1fce31df54a8d 100644 --- a/posthog/api/test/test_insight.py +++ b/posthog/api/test/test_insight.py @@ -1145,10 +1145,10 @@ def test_insight_refreshing_legacy_conversion(self) -> None: [ [ # Property group filter, which is what's actually used these days PropertyGroupFilter( - type=FilterLogicalOperator.AND, + type=FilterLogicalOperator.AND_, values=[ PropertyGroupFilterValue( - type=FilterLogicalOperator.OR, + type=FilterLogicalOperator.OR_, values=[EventPropertyFilter(key="another", value="never_return_this", operator="is_not")], ) ], @@ -1377,10 +1377,10 @@ def test_insight_refreshing_legacy_with_background_update(self, spy_calculate_ta [ [ # Property group filter, which is what's actually used these days PropertyGroupFilter( - type=FilterLogicalOperator.AND, + type=FilterLogicalOperator.AND_, values=[ PropertyGroupFilterValue( - type=FilterLogicalOperator.OR, + type=FilterLogicalOperator.OR_, values=[EventPropertyFilter(key="another", value="never_return_this", operator="is_not")], ) ], @@ -1461,10 +1461,10 @@ def test_insight_refreshing_query_with_background_update( [ [ # Property group filter, which is what's actually used these days PropertyGroupFilter( - type=FilterLogicalOperator.AND, + type=FilterLogicalOperator.AND_, values=[ PropertyGroupFilterValue( - type=FilterLogicalOperator.OR, + type=FilterLogicalOperator.OR_, values=[EventPropertyFilter(key="another", value="never_return_this", operator="is_not")], ) ], diff --git a/posthog/api/test/test_query.py b/posthog/api/test/test_query.py index 099aebf055697..4ec129186fcb9 100644 --- a/posthog/api/test/test_query.py +++ b/posthog/api/test/test_query.py @@ -261,7 +261,7 @@ def test_event_property_filter(self): type="event", key="key", value="test_val3", - operator=PropertyOperator.exact, + operator=PropertyOperator.EXACT, ) ] response = self.client.post(f"/api/projects/{self.team.id}/query/", {"query": query.dict()}).json() @@ -272,7 +272,7 @@ def test_event_property_filter(self): type="event", key="path", value="/", - operator=PropertyOperator.icontains, + operator=PropertyOperator.ICONTAINS, ) ] response = self.client.post(f"/api/projects/{self.team.id}/query/", {"query": query.dict()}).json() @@ -331,7 +331,7 @@ def test_person_property_filter(self): type="person", key="email", value="tom@posthog.com", - operator=PropertyOperator.exact, + operator=PropertyOperator.EXACT, ) ], ) diff --git a/posthog/celery.py b/posthog/celery.py index 29c45c9b60729..00c039de17864 100644 --- a/posthog/celery.py +++ b/posthog/celery.py @@ -43,7 +43,7 @@ CELERY_TASK_RETRY_COUNTER = Counter( "posthog_celery_task_retry", "task retry signal is dispatched when a task will be retried.", - labelnames=["task_name"], + labelnames=["task_name", "reason"], ) @@ -145,8 +145,8 @@ def failure_signal_handler(sender, **kwargs): @task_retry.connect -def retry_signal_handler(sender, **kwargs): - CELERY_TASK_RETRY_COUNTER.labels(task_name=sender.name).inc() +def retry_signal_handler(sender, reason, **kwargs): + CELERY_TASK_RETRY_COUNTER.labels(task_name=sender.name, reason=str(reason)).inc() @app.on_after_finalize.connect diff --git a/posthog/hogql/ast.py b/posthog/hogql/ast.py index d296a354b7c50..d9e71e34e4cac 100644 --- a/posthog/hogql/ast.py +++ b/posthog/hogql/ast.py @@ -717,6 +717,7 @@ class WindowExpr(Expr): class WindowFunction(Expr): name: str args: Optional[list[Expr]] = None + exprs: Optional[list[Expr]] = None over_expr: Optional[WindowExpr] = None over_identifier: Optional[str] = None diff --git a/posthog/hogql/autocomplete.py b/posthog/hogql/autocomplete.py index 4440368bccc1b..0f73304061f33 100644 --- a/posthog/hogql/autocomplete.py +++ b/posthog/hogql/autocomplete.py @@ -258,7 +258,7 @@ def append_table_field_to_response(table: Table, suggestions: list[AutocompleteC extend_responses( available_functions, suggestions, - Kind.Function, + Kind.FUNCTION, insert_text=lambda key: f"{key}()", ) @@ -266,7 +266,7 @@ def append_table_field_to_response(table: Table, suggestions: list[AutocompleteC def extend_responses( keys: list[str], suggestions: list[AutocompleteCompletionItem], - kind: Kind = Kind.Variable, + kind: Kind = Kind.VARIABLE, insert_text: Optional[Callable[[str], str]] = None, details: Optional[list[str | None]] = None, ) -> None: @@ -365,7 +365,7 @@ def get_hogql_autocomplete( extend_responses( keys=table_aliases, suggestions=response.suggestions, - kind=Kind.Folder, + kind=Kind.FOLDER, details=["Table"] * len(table_aliases), ) break @@ -459,7 +459,7 @@ def get_hogql_autocomplete( extend_responses( keys=table_names, suggestions=response.suggestions, - kind=Kind.Folder, + kind=Kind.FOLDER, details=["Table"] * len(table_names), ) except Exception: diff --git a/posthog/hogql/database/database.py b/posthog/hogql/database/database.py index e7a21888e112d..ce714239d43d7 100644 --- a/posthog/hogql/database/database.py +++ b/posthog/hogql/database/database.py @@ -217,21 +217,21 @@ def create_hogql_database( modifiers = create_default_modifiers_for_team(team, modifiers) database = Database(timezone=team.timezone, week_start_day=team.week_start_day) - if modifiers.personsOnEventsMode == PersonsOnEventsMode.disabled: + if modifiers.personsOnEventsMode == PersonsOnEventsMode.DISABLED: # no change database.events.fields["person"] = FieldTraverser(chain=["pdi", "person"]) database.events.fields["person_id"] = FieldTraverser(chain=["pdi", "person_id"]) - elif modifiers.personsOnEventsMode == PersonsOnEventsMode.person_id_no_override_properties_on_events: + elif modifiers.personsOnEventsMode == PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS: database.events.fields["person_id"] = StringDatabaseField(name="person_id") _use_person_properties_from_events(database) - elif modifiers.personsOnEventsMode == PersonsOnEventsMode.person_id_override_properties_on_events: + elif modifiers.personsOnEventsMode == PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS: _use_person_id_from_person_overrides(database) _use_person_properties_from_events(database) database.events.fields["poe"].fields["id"] = database.events.fields["person_id"] - elif modifiers.personsOnEventsMode == PersonsOnEventsMode.person_id_override_properties_joined: + elif modifiers.personsOnEventsMode == PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_JOINED: _use_person_id_from_person_overrides(database) database.events.fields["person"] = LazyJoin( from_field=["person_id"], @@ -509,23 +509,23 @@ def serialize_database( def constant_type_to_serialized_field_type(constant_type: ast.ConstantType) -> DatabaseSerializedFieldType | None: if isinstance(constant_type, ast.StringType): - return DatabaseSerializedFieldType.string + return DatabaseSerializedFieldType.STRING if isinstance(constant_type, ast.BooleanType): - return DatabaseSerializedFieldType.boolean + return DatabaseSerializedFieldType.BOOLEAN if isinstance(constant_type, ast.DateType): - return DatabaseSerializedFieldType.date + return DatabaseSerializedFieldType.DATE if isinstance(constant_type, ast.DateTimeType): - return DatabaseSerializedFieldType.datetime + return DatabaseSerializedFieldType.DATETIME if isinstance(constant_type, ast.UUIDType): - return DatabaseSerializedFieldType.string + return DatabaseSerializedFieldType.STRING if isinstance(constant_type, ast.ArrayType): - return DatabaseSerializedFieldType.array + return DatabaseSerializedFieldType.ARRAY if isinstance(constant_type, ast.TupleType): - return DatabaseSerializedFieldType.json + return DatabaseSerializedFieldType.JSON if isinstance(constant_type, ast.IntegerType): - return DatabaseSerializedFieldType.integer + return DatabaseSerializedFieldType.INTEGER if isinstance(constant_type, ast.FloatType): - return DatabaseSerializedFieldType.float + return DatabaseSerializedFieldType.FLOAT return None @@ -569,7 +569,7 @@ def serialize_fields( DatabaseSchemaField( name=field_key, hogql_value=hogql_value, - type=DatabaseSerializedFieldType.integer, + type=DatabaseSerializedFieldType.INTEGER, schema_valid=schema_valid, ) ) @@ -578,7 +578,7 @@ def serialize_fields( DatabaseSchemaField( name=field_key, hogql_value=hogql_value, - type=DatabaseSerializedFieldType.float, + type=DatabaseSerializedFieldType.FLOAT, schema_valid=schema_valid, ) ) @@ -587,7 +587,7 @@ def serialize_fields( DatabaseSchemaField( name=field_key, hogql_value=hogql_value, - type=DatabaseSerializedFieldType.string, + type=DatabaseSerializedFieldType.STRING, schema_valid=schema_valid, ) ) @@ -596,7 +596,7 @@ def serialize_fields( DatabaseSchemaField( name=field_key, hogql_value=hogql_value, - type=DatabaseSerializedFieldType.datetime, + type=DatabaseSerializedFieldType.DATETIME, schema_valid=schema_valid, ) ) @@ -605,7 +605,7 @@ def serialize_fields( DatabaseSchemaField( name=field_key, hogql_value=hogql_value, - type=DatabaseSerializedFieldType.date, + type=DatabaseSerializedFieldType.DATE, schema_valid=schema_valid, ) ) @@ -614,7 +614,7 @@ def serialize_fields( DatabaseSchemaField( name=field_key, hogql_value=hogql_value, - type=DatabaseSerializedFieldType.boolean, + type=DatabaseSerializedFieldType.BOOLEAN, schema_valid=schema_valid, ) ) @@ -623,7 +623,7 @@ def serialize_fields( DatabaseSchemaField( name=field_key, hogql_value=hogql_value, - type=DatabaseSerializedFieldType.json, + type=DatabaseSerializedFieldType.JSON, schema_valid=schema_valid, ) ) @@ -632,7 +632,7 @@ def serialize_fields( DatabaseSchemaField( name=field_key, hogql_value=hogql_value, - type=DatabaseSerializedFieldType.array, + type=DatabaseSerializedFieldType.ARRAY, schema_valid=schema_valid, ) ) @@ -643,7 +643,7 @@ def serialize_fields( field_type = constant_type_to_serialized_field_type(constant_type) if field_type is None: - field_type = DatabaseSerializedFieldType.expression + field_type = DatabaseSerializedFieldType.EXPRESSION field_output.append( DatabaseSchemaField( @@ -659,7 +659,7 @@ def serialize_fields( DatabaseSchemaField( name=field_key, hogql_value=hogql_value, - type=DatabaseSerializedFieldType.view if is_view else DatabaseSerializedFieldType.lazy_table, + type=DatabaseSerializedFieldType.VIEW if is_view else DatabaseSerializedFieldType.LAZY_TABLE, schema_valid=schema_valid, table=field.resolve_table(context).to_printed_hogql(), fields=list(field.resolve_table(context).fields.keys()), @@ -670,7 +670,7 @@ def serialize_fields( DatabaseSchemaField( name=field_key, hogql_value=hogql_value, - type=DatabaseSerializedFieldType.virtual_table, + type=DatabaseSerializedFieldType.VIRTUAL_TABLE, schema_valid=schema_valid, table=field.to_printed_hogql(), fields=list(field.fields.keys()), @@ -681,7 +681,7 @@ def serialize_fields( DatabaseSchemaField( name=field_key, hogql_value=hogql_value, - type=DatabaseSerializedFieldType.field_traverser, + type=DatabaseSerializedFieldType.FIELD_TRAVERSER, schema_valid=schema_valid, chain=field.chain, ) diff --git a/posthog/hogql/database/schema/persons.py b/posthog/hogql/database/schema/persons.py index 2af0a95381b67..54cf36645f506 100644 --- a/posthog/hogql/database/schema/persons.py +++ b/posthog/hogql/database/schema/persons.py @@ -40,15 +40,15 @@ def select_from_persons_table(join_or_table: LazyJoinToAdd | LazyTableToAdd, context: HogQLContext, node: SelectQuery): version = context.modifiers.personsArgMaxVersion - if version == PersonsArgMaxVersion.auto: - version = PersonsArgMaxVersion.v1 + if version == PersonsArgMaxVersion.AUTO: + version = PersonsArgMaxVersion.V1 # If selecting properties, use the faster v2 query. Otherwise, v1 is faster. for field_chain in join_or_table.fields_accessed.values(): if field_chain[0] == "properties": - version = PersonsArgMaxVersion.v2 + version = PersonsArgMaxVersion.V2 break - if version == PersonsArgMaxVersion.v2: + if version == PersonsArgMaxVersion.V2: from posthog.hogql import ast from posthog.hogql.parser import parse_select diff --git a/posthog/hogql/database/schema/sessions.py b/posthog/hogql/database/schema/sessions.py index 586da5d102206..fba4f4656b012 100644 --- a/posthog/hogql/database/schema/sessions.py +++ b/posthog/hogql/database/schema/sessions.py @@ -198,7 +198,7 @@ def arg_max_merge_field(field_name: str) -> ast.Call: args=[aggregate_fields["$urls"]], ) - if context.modifiers.bounceRatePageViewMode == BounceRatePageViewMode.uniq_urls: + if context.modifiers.bounceRatePageViewMode == BounceRatePageViewMode.UNIQ_URLS: bounce_pageview_count = aggregate_fields["$num_uniq_urls"] else: bounce_pageview_count = aggregate_fields["$pageview_count"] diff --git a/posthog/hogql/database/schema/test/test_sessions.py b/posthog/hogql/database/schema/test/test_sessions.py index 53e13beee53c5..e17f8208b567c 100644 --- a/posthog/hogql/database/schema/test/test_sessions.py +++ b/posthog/hogql/database/schema/test/test_sessions.py @@ -142,7 +142,7 @@ def test_persons_and_sessions_on_events(self): self.assertEqual(row1, (p1.uuid, "source1")) self.assertEqual(row2, (p2.uuid, "source2")) - @parameterized.expand([(BounceRatePageViewMode.uniq_urls,), (BounceRatePageViewMode.count_pageviews,)]) + @parameterized.expand([(BounceRatePageViewMode.UNIQ_URLS,), (BounceRatePageViewMode.COUNT_PAGEVIEWS,)]) def test_bounce_rate(self, bounceRatePageViewMode): # person with 2 different sessions _create_event( diff --git a/posthog/hogql/database/schema/util/test/test_person_where_clause_extractor.py b/posthog/hogql/database/schema/util/test/test_person_where_clause_extractor.py index 65f2be22a2c17..b58a03fe15647 100644 --- a/posthog/hogql/database/schema/util/test/test_person_where_clause_extractor.py +++ b/posthog/hogql/database/schema/util/test/test_person_where_clause_extractor.py @@ -40,8 +40,8 @@ def get_clause(self, query: str): team = self.team modifiers = create_default_modifiers_for_team(team) modifiers.optimizeJoinedFilters = True - modifiers.personsOnEventsMode = PersonsOnEventsMode.disabled - modifiers.personsArgMaxVersion = PersonsArgMaxVersion.v1 + modifiers.personsOnEventsMode = PersonsOnEventsMode.DISABLED + modifiers.personsArgMaxVersion = PersonsArgMaxVersion.V1 context = HogQLContext( team_id=team.pk, team=team, diff --git a/posthog/hogql/database/test/test_database.py b/posthog/hogql/database/test/test_database.py index 3e733b4cb22db..cb380bce9da94 100644 --- a/posthog/hogql/database/test/test_database.py +++ b/posthog/hogql/database/test/test_database.py @@ -474,7 +474,7 @@ def test_selecting_persons_from_events_ignores_future_persons(self): database=db, # disable PoE modifiers=create_default_modifiers_for_team( - self.team, HogQLQueryModifiers(personsOnEventsMode=PersonsOnEventsMode.disabled) + self.team, HogQLQueryModifiers(personsOnEventsMode=PersonsOnEventsMode.DISABLED) ), ) sql = "select person.id from events" diff --git a/posthog/hogql/functions/mapping.py b/posthog/hogql/functions/mapping.py index 2c7052905d46d..bda37830c4b37 100644 --- a/posthog/hogql/functions/mapping.py +++ b/posthog/hogql/functions/mapping.py @@ -854,6 +854,7 @@ def compare_types(arg_types: list[ConstantType], sig_arg_types: tuple[ConstantTy "covarPopIf": HogQLFunctionMeta("covarPopIf", 3, 3, aggregate=True), "covarSamp": HogQLFunctionMeta("covarSamp", 2, 2, aggregate=True), "covarSampIf": HogQLFunctionMeta("covarSampIf", 3, 3, aggregate=True), + "corr": HogQLFunctionMeta("corr", 2, 2, aggregate=True), # ClickHouse-specific aggregate functions "anyHeavy": HogQLFunctionMeta("anyHeavy", 1, 1, aggregate=True), "anyHeavyIf": HogQLFunctionMeta("anyHeavyIf", 2, 2, aggregate=True), diff --git a/posthog/hogql/grammar/HogQLParser.g4 b/posthog/hogql/grammar/HogQLParser.g4 index bc3c954de10aa..911f5827073d0 100644 --- a/posthog/hogql/grammar/HogQLParser.g4 +++ b/posthog/hogql/grammar/HogQLParser.g4 @@ -140,9 +140,9 @@ columnExpr | SUBSTRING LPAREN columnExpr FROM columnExpr (FOR columnExpr)? RPAREN # ColumnExprSubstring | TIMESTAMP STRING_LITERAL # ColumnExprTimestamp | TRIM LPAREN (BOTH | LEADING | TRAILING) string FROM columnExpr RPAREN # ColumnExprTrim - | identifier (LPAREN columnExprList? RPAREN) OVER LPAREN windowExpr RPAREN # ColumnExprWinFunction - | identifier (LPAREN columnExprList? RPAREN) OVER identifier # ColumnExprWinFunctionTarget - | identifier (LPAREN columnExprList? RPAREN)? LPAREN DISTINCT? columnArgList? RPAREN # ColumnExprFunction + | identifier (LPAREN columnExprList? RPAREN) (LPAREN DISTINCT? columnArgList? RPAREN)? OVER LPAREN windowExpr RPAREN # ColumnExprWinFunction + | identifier (LPAREN columnExprList? RPAREN) (LPAREN DISTINCT? columnArgList? RPAREN)? OVER identifier # ColumnExprWinFunctionTarget + | identifier (LPAREN columnExprList? RPAREN)? LPAREN DISTINCT? columnArgList? RPAREN # ColumnExprFunction | hogqlxTagElement # ColumnExprTagElement | templateString # ColumnExprTemplateString | literal # ColumnExprLiteral diff --git a/posthog/hogql/grammar/HogQLParser.interp b/posthog/hogql/grammar/HogQLParser.interp index a2f030a7eb8fa..086eca220c32f 100644 --- a/posthog/hogql/grammar/HogQLParser.interp +++ b/posthog/hogql/grammar/HogQLParser.interp @@ -399,4 +399,4 @@ stringContentsFull atn: -[4, 1, 154, 1158, 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, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, 41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, 2, 47, 7, 47, 2, 48, 7, 48, 2, 49, 7, 49, 2, 50, 7, 50, 2, 51, 7, 51, 2, 52, 7, 52, 2, 53, 7, 53, 2, 54, 7, 54, 2, 55, 7, 55, 2, 56, 7, 56, 2, 57, 7, 57, 2, 58, 7, 58, 2, 59, 7, 59, 2, 60, 7, 60, 2, 61, 7, 61, 2, 62, 7, 62, 2, 63, 7, 63, 2, 64, 7, 64, 2, 65, 7, 65, 2, 66, 7, 66, 2, 67, 7, 67, 2, 68, 7, 68, 2, 69, 7, 69, 2, 70, 7, 70, 2, 71, 7, 71, 2, 72, 7, 72, 2, 73, 7, 73, 2, 74, 7, 74, 2, 75, 7, 75, 2, 76, 7, 76, 2, 77, 7, 77, 2, 78, 7, 78, 2, 79, 7, 79, 2, 80, 7, 80, 2, 81, 7, 81, 2, 82, 7, 82, 1, 0, 5, 0, 168, 8, 0, 10, 0, 12, 0, 171, 9, 0, 1, 0, 1, 0, 1, 1, 1, 1, 3, 1, 177, 8, 1, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 3, 3, 186, 8, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 5, 5, 199, 8, 5, 10, 5, 12, 5, 202, 9, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 3, 6, 213, 8, 6, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 3, 8, 225, 8, 8, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, 1, 11, 3, 11, 241, 8, 11, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 13, 1, 13, 5, 13, 250, 8, 13, 10, 13, 12, 13, 253, 9, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 1, 14, 1, 15, 1, 15, 1, 15, 5, 15, 264, 8, 15, 10, 15, 12, 15, 267, 9, 15, 1, 16, 1, 16, 1, 16, 3, 16, 272, 8, 16, 1, 16, 1, 16, 1, 17, 1, 17, 1, 17, 1, 17, 5, 17, 280, 8, 17, 10, 17, 12, 17, 283, 9, 17, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 3, 18, 291, 8, 18, 1, 19, 3, 19, 294, 8, 19, 1, 19, 1, 19, 3, 19, 298, 8, 19, 1, 19, 3, 19, 301, 8, 19, 1, 19, 1, 19, 3, 19, 305, 8, 19, 1, 19, 3, 19, 308, 8, 19, 1, 19, 3, 19, 311, 8, 19, 1, 19, 3, 19, 314, 8, 19, 1, 19, 3, 19, 317, 8, 19, 1, 19, 1, 19, 3, 19, 321, 8, 19, 1, 19, 1, 19, 3, 19, 325, 8, 19, 1, 19, 3, 19, 328, 8, 19, 1, 19, 3, 19, 331, 8, 19, 1, 19, 3, 19, 334, 8, 19, 1, 19, 1, 19, 3, 19, 338, 8, 19, 1, 19, 3, 19, 341, 8, 19, 1, 20, 1, 20, 1, 20, 1, 21, 1, 21, 1, 21, 1, 21, 3, 21, 350, 8, 21, 1, 22, 1, 22, 1, 22, 1, 23, 3, 23, 356, 8, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 5, 24, 375, 8, 24, 10, 24, 12, 24, 378, 9, 24, 1, 25, 1, 25, 1, 25, 1, 26, 1, 26, 1, 26, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 3, 27, 394, 8, 27, 1, 28, 1, 28, 1, 28, 1, 29, 1, 29, 1, 29, 1, 29, 1, 30, 1, 30, 1, 30, 1, 30, 1, 31, 1, 31, 1, 31, 1, 31, 3, 31, 411, 8, 31, 1, 31, 1, 31, 1, 31, 1, 31, 3, 31, 417, 8, 31, 1, 31, 1, 31, 1, 31, 1, 31, 3, 31, 423, 8, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 3, 31, 434, 8, 31, 3, 31, 436, 8, 31, 1, 32, 1, 32, 1, 32, 1, 33, 1, 33, 1, 33, 1, 34, 1, 34, 1, 34, 3, 34, 447, 8, 34, 1, 34, 3, 34, 450, 8, 34, 1, 34, 1, 34, 1, 34, 1, 34, 3, 34, 456, 8, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 3, 34, 464, 8, 34, 1, 34, 1, 34, 1, 34, 1, 34, 5, 34, 470, 8, 34, 10, 34, 12, 34, 473, 9, 34, 1, 35, 3, 35, 476, 8, 35, 1, 35, 1, 35, 1, 35, 3, 35, 481, 8, 35, 1, 35, 3, 35, 484, 8, 35, 1, 35, 3, 35, 487, 8, 35, 1, 35, 1, 35, 3, 35, 491, 8, 35, 1, 35, 1, 35, 3, 35, 495, 8, 35, 1, 35, 3, 35, 498, 8, 35, 3, 35, 500, 8, 35, 1, 35, 3, 35, 503, 8, 35, 1, 35, 1, 35, 3, 35, 507, 8, 35, 1, 35, 1, 35, 3, 35, 511, 8, 35, 1, 35, 3, 35, 514, 8, 35, 3, 35, 516, 8, 35, 3, 35, 518, 8, 35, 1, 36, 1, 36, 1, 36, 3, 36, 523, 8, 36, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 3, 37, 534, 8, 37, 1, 38, 1, 38, 1, 38, 1, 38, 3, 38, 540, 8, 38, 1, 39, 1, 39, 1, 39, 5, 39, 545, 8, 39, 10, 39, 12, 39, 548, 9, 39, 1, 40, 1, 40, 3, 40, 552, 8, 40, 1, 40, 1, 40, 3, 40, 556, 8, 40, 1, 40, 1, 40, 3, 40, 560, 8, 40, 1, 41, 1, 41, 1, 41, 1, 41, 3, 41, 566, 8, 41, 3, 41, 568, 8, 41, 1, 42, 1, 42, 1, 42, 5, 42, 573, 8, 42, 10, 42, 12, 42, 576, 9, 42, 1, 43, 1, 43, 1, 43, 1, 43, 1, 44, 3, 44, 583, 8, 44, 1, 44, 3, 44, 586, 8, 44, 1, 44, 3, 44, 589, 8, 44, 1, 45, 1, 45, 1, 45, 1, 45, 1, 46, 1, 46, 1, 46, 1, 46, 1, 47, 1, 47, 1, 47, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 3, 48, 608, 8, 48, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 3, 49, 622, 8, 49, 1, 50, 1, 50, 1, 50, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 5, 51, 636, 8, 51, 10, 51, 12, 51, 639, 9, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 5, 51, 648, 8, 51, 10, 51, 12, 51, 651, 9, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 5, 51, 660, 8, 51, 10, 51, 12, 51, 663, 9, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 3, 51, 670, 8, 51, 1, 51, 1, 51, 3, 51, 674, 8, 51, 1, 52, 1, 52, 1, 52, 5, 52, 679, 8, 52, 10, 52, 12, 52, 682, 9, 52, 1, 53, 1, 53, 1, 53, 3, 53, 687, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 4, 53, 694, 8, 53, 11, 53, 12, 53, 695, 1, 53, 1, 53, 3, 53, 700, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 724, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 741, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 753, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 763, 8, 53, 1, 53, 3, 53, 766, 8, 53, 1, 53, 1, 53, 3, 53, 770, 8, 53, 1, 53, 3, 53, 773, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 787, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 804, 8, 53, 1, 53, 1, 53, 1, 53, 3, 53, 809, 8, 53, 1, 53, 1, 53, 3, 53, 813, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 819, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 826, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 838, 8, 53, 1, 53, 1, 53, 3, 53, 842, 8, 53, 1, 53, 3, 53, 845, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 854, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 868, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 895, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 904, 8, 53, 5, 53, 906, 8, 53, 10, 53, 12, 53, 909, 9, 53, 1, 54, 1, 54, 1, 54, 5, 54, 914, 8, 54, 10, 54, 12, 54, 917, 9, 54, 1, 55, 1, 55, 3, 55, 921, 8, 55, 1, 56, 1, 56, 1, 56, 1, 56, 5, 56, 927, 8, 56, 10, 56, 12, 56, 930, 9, 56, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 5, 56, 937, 8, 56, 10, 56, 12, 56, 940, 9, 56, 3, 56, 942, 8, 56, 1, 56, 1, 56, 1, 56, 1, 57, 1, 57, 1, 57, 5, 57, 950, 8, 57, 10, 57, 12, 57, 953, 9, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 5, 57, 961, 8, 57, 10, 57, 12, 57, 964, 9, 57, 1, 57, 1, 57, 3, 57, 968, 8, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 3, 57, 975, 8, 57, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 3, 58, 988, 8, 58, 1, 59, 1, 59, 1, 59, 5, 59, 993, 8, 59, 10, 59, 12, 59, 996, 9, 59, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 3, 60, 1008, 8, 60, 1, 61, 1, 61, 1, 61, 1, 61, 3, 61, 1014, 8, 61, 1, 61, 3, 61, 1017, 8, 61, 1, 62, 1, 62, 1, 62, 5, 62, 1022, 8, 62, 10, 62, 12, 62, 1025, 9, 62, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 3, 63, 1036, 8, 63, 1, 63, 1, 63, 1, 63, 1, 63, 3, 63, 1042, 8, 63, 5, 63, 1044, 8, 63, 10, 63, 12, 63, 1047, 9, 63, 1, 64, 1, 64, 1, 64, 3, 64, 1052, 8, 64, 1, 64, 1, 64, 1, 65, 1, 65, 1, 65, 3, 65, 1059, 8, 65, 1, 65, 1, 65, 1, 66, 1, 66, 1, 66, 5, 66, 1066, 8, 66, 10, 66, 12, 66, 1069, 9, 66, 1, 67, 1, 67, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 3, 68, 1079, 8, 68, 3, 68, 1081, 8, 68, 1, 69, 3, 69, 1084, 8, 69, 1, 69, 1, 69, 1, 69, 1, 69, 1, 69, 1, 69, 3, 69, 1092, 8, 69, 1, 70, 1, 70, 1, 70, 3, 70, 1097, 8, 70, 1, 71, 1, 71, 1, 72, 1, 72, 1, 73, 1, 73, 1, 74, 1, 74, 3, 74, 1107, 8, 74, 1, 75, 1, 75, 1, 75, 3, 75, 1112, 8, 75, 1, 76, 1, 76, 1, 76, 1, 76, 1, 77, 1, 77, 1, 77, 1, 77, 1, 78, 1, 78, 3, 78, 1124, 8, 78, 1, 79, 1, 79, 5, 79, 1128, 8, 79, 10, 79, 12, 79, 1131, 9, 79, 1, 79, 1, 79, 1, 80, 1, 80, 1, 80, 1, 80, 1, 80, 3, 80, 1140, 8, 80, 1, 81, 1, 81, 5, 81, 1144, 8, 81, 10, 81, 12, 81, 1147, 9, 81, 1, 81, 1, 81, 1, 82, 1, 82, 1, 82, 1, 82, 1, 82, 3, 82, 1156, 8, 82, 1, 82, 0, 3, 68, 106, 126, 83, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 0, 16, 2, 0, 17, 17, 72, 72, 2, 0, 42, 42, 49, 49, 3, 0, 1, 1, 4, 4, 8, 8, 4, 0, 1, 1, 3, 4, 8, 8, 78, 78, 2, 0, 49, 49, 71, 71, 2, 0, 1, 1, 4, 4, 2, 0, 7, 7, 21, 22, 2, 0, 28, 28, 47, 47, 2, 0, 69, 69, 74, 74, 3, 0, 10, 10, 48, 48, 87, 87, 2, 0, 39, 39, 51, 51, 1, 0, 103, 104, 2, 0, 114, 114, 134, 134, 7, 0, 20, 20, 36, 36, 53, 54, 68, 68, 76, 76, 93, 93, 99, 99, 12, 0, 1, 19, 21, 28, 30, 35, 37, 40, 42, 49, 51, 52, 56, 56, 58, 67, 69, 75, 77, 92, 94, 95, 97, 98, 4, 0, 19, 19, 28, 28, 37, 37, 46, 46, 1288, 0, 169, 1, 0, 0, 0, 2, 176, 1, 0, 0, 0, 4, 178, 1, 0, 0, 0, 6, 180, 1, 0, 0, 0, 8, 189, 1, 0, 0, 0, 10, 195, 1, 0, 0, 0, 12, 212, 1, 0, 0, 0, 14, 214, 1, 0, 0, 0, 16, 217, 1, 0, 0, 0, 18, 226, 1, 0, 0, 0, 20, 232, 1, 0, 0, 0, 22, 236, 1, 0, 0, 0, 24, 245, 1, 0, 0, 0, 26, 247, 1, 0, 0, 0, 28, 256, 1, 0, 0, 0, 30, 260, 1, 0, 0, 0, 32, 271, 1, 0, 0, 0, 34, 275, 1, 0, 0, 0, 36, 290, 1, 0, 0, 0, 38, 293, 1, 0, 0, 0, 40, 342, 1, 0, 0, 0, 42, 345, 1, 0, 0, 0, 44, 351, 1, 0, 0, 0, 46, 355, 1, 0, 0, 0, 48, 361, 1, 0, 0, 0, 50, 379, 1, 0, 0, 0, 52, 382, 1, 0, 0, 0, 54, 385, 1, 0, 0, 0, 56, 395, 1, 0, 0, 0, 58, 398, 1, 0, 0, 0, 60, 402, 1, 0, 0, 0, 62, 435, 1, 0, 0, 0, 64, 437, 1, 0, 0, 0, 66, 440, 1, 0, 0, 0, 68, 455, 1, 0, 0, 0, 70, 517, 1, 0, 0, 0, 72, 522, 1, 0, 0, 0, 74, 533, 1, 0, 0, 0, 76, 535, 1, 0, 0, 0, 78, 541, 1, 0, 0, 0, 80, 549, 1, 0, 0, 0, 82, 567, 1, 0, 0, 0, 84, 569, 1, 0, 0, 0, 86, 577, 1, 0, 0, 0, 88, 582, 1, 0, 0, 0, 90, 590, 1, 0, 0, 0, 92, 594, 1, 0, 0, 0, 94, 598, 1, 0, 0, 0, 96, 607, 1, 0, 0, 0, 98, 621, 1, 0, 0, 0, 100, 623, 1, 0, 0, 0, 102, 673, 1, 0, 0, 0, 104, 675, 1, 0, 0, 0, 106, 812, 1, 0, 0, 0, 108, 910, 1, 0, 0, 0, 110, 920, 1, 0, 0, 0, 112, 941, 1, 0, 0, 0, 114, 974, 1, 0, 0, 0, 116, 987, 1, 0, 0, 0, 118, 989, 1, 0, 0, 0, 120, 1007, 1, 0, 0, 0, 122, 1016, 1, 0, 0, 0, 124, 1018, 1, 0, 0, 0, 126, 1035, 1, 0, 0, 0, 128, 1048, 1, 0, 0, 0, 130, 1058, 1, 0, 0, 0, 132, 1062, 1, 0, 0, 0, 134, 1070, 1, 0, 0, 0, 136, 1080, 1, 0, 0, 0, 138, 1083, 1, 0, 0, 0, 140, 1096, 1, 0, 0, 0, 142, 1098, 1, 0, 0, 0, 144, 1100, 1, 0, 0, 0, 146, 1102, 1, 0, 0, 0, 148, 1106, 1, 0, 0, 0, 150, 1111, 1, 0, 0, 0, 152, 1113, 1, 0, 0, 0, 154, 1117, 1, 0, 0, 0, 156, 1123, 1, 0, 0, 0, 158, 1125, 1, 0, 0, 0, 160, 1139, 1, 0, 0, 0, 162, 1141, 1, 0, 0, 0, 164, 1155, 1, 0, 0, 0, 166, 168, 3, 2, 1, 0, 167, 166, 1, 0, 0, 0, 168, 171, 1, 0, 0, 0, 169, 167, 1, 0, 0, 0, 169, 170, 1, 0, 0, 0, 170, 172, 1, 0, 0, 0, 171, 169, 1, 0, 0, 0, 172, 173, 5, 0, 0, 1, 173, 1, 1, 0, 0, 0, 174, 177, 3, 6, 3, 0, 175, 177, 3, 12, 6, 0, 176, 174, 1, 0, 0, 0, 176, 175, 1, 0, 0, 0, 177, 3, 1, 0, 0, 0, 178, 179, 3, 106, 53, 0, 179, 5, 1, 0, 0, 0, 180, 181, 5, 50, 0, 0, 181, 185, 3, 150, 75, 0, 182, 183, 5, 111, 0, 0, 183, 184, 5, 118, 0, 0, 184, 186, 3, 4, 2, 0, 185, 182, 1, 0, 0, 0, 185, 186, 1, 0, 0, 0, 186, 187, 1, 0, 0, 0, 187, 188, 5, 145, 0, 0, 188, 7, 1, 0, 0, 0, 189, 190, 3, 4, 2, 0, 190, 191, 5, 111, 0, 0, 191, 192, 5, 118, 0, 0, 192, 193, 3, 4, 2, 0, 193, 194, 5, 145, 0, 0, 194, 9, 1, 0, 0, 0, 195, 200, 3, 150, 75, 0, 196, 197, 5, 112, 0, 0, 197, 199, 3, 150, 75, 0, 198, 196, 1, 0, 0, 0, 199, 202, 1, 0, 0, 0, 200, 198, 1, 0, 0, 0, 200, 201, 1, 0, 0, 0, 201, 11, 1, 0, 0, 0, 202, 200, 1, 0, 0, 0, 203, 213, 3, 20, 10, 0, 204, 213, 3, 24, 12, 0, 205, 213, 3, 14, 7, 0, 206, 213, 3, 16, 8, 0, 207, 213, 3, 18, 9, 0, 208, 213, 3, 22, 11, 0, 209, 213, 3, 8, 4, 0, 210, 213, 3, 20, 10, 0, 211, 213, 3, 26, 13, 0, 212, 203, 1, 0, 0, 0, 212, 204, 1, 0, 0, 0, 212, 205, 1, 0, 0, 0, 212, 206, 1, 0, 0, 0, 212, 207, 1, 0, 0, 0, 212, 208, 1, 0, 0, 0, 212, 209, 1, 0, 0, 0, 212, 210, 1, 0, 0, 0, 212, 211, 1, 0, 0, 0, 213, 13, 1, 0, 0, 0, 214, 215, 3, 4, 2, 0, 215, 216, 5, 145, 0, 0, 216, 15, 1, 0, 0, 0, 217, 218, 5, 38, 0, 0, 218, 219, 5, 126, 0, 0, 219, 220, 3, 4, 2, 0, 220, 221, 5, 144, 0, 0, 221, 224, 3, 12, 6, 0, 222, 223, 5, 24, 0, 0, 223, 225, 3, 12, 6, 0, 224, 222, 1, 0, 0, 0, 224, 225, 1, 0, 0, 0, 225, 17, 1, 0, 0, 0, 226, 227, 5, 96, 0, 0, 227, 228, 5, 126, 0, 0, 228, 229, 3, 4, 2, 0, 229, 230, 5, 144, 0, 0, 230, 231, 3, 12, 6, 0, 231, 19, 1, 0, 0, 0, 232, 233, 5, 70, 0, 0, 233, 234, 3, 4, 2, 0, 234, 235, 5, 145, 0, 0, 235, 21, 1, 0, 0, 0, 236, 237, 5, 29, 0, 0, 237, 238, 3, 150, 75, 0, 238, 240, 5, 126, 0, 0, 239, 241, 3, 10, 5, 0, 240, 239, 1, 0, 0, 0, 240, 241, 1, 0, 0, 0, 241, 242, 1, 0, 0, 0, 242, 243, 5, 144, 0, 0, 243, 244, 3, 26, 13, 0, 244, 23, 1, 0, 0, 0, 245, 246, 5, 145, 0, 0, 246, 25, 1, 0, 0, 0, 247, 251, 5, 124, 0, 0, 248, 250, 3, 2, 1, 0, 249, 248, 1, 0, 0, 0, 250, 253, 1, 0, 0, 0, 251, 249, 1, 0, 0, 0, 251, 252, 1, 0, 0, 0, 252, 254, 1, 0, 0, 0, 253, 251, 1, 0, 0, 0, 254, 255, 5, 142, 0, 0, 255, 27, 1, 0, 0, 0, 256, 257, 3, 4, 2, 0, 257, 258, 5, 111, 0, 0, 258, 259, 3, 4, 2, 0, 259, 29, 1, 0, 0, 0, 260, 265, 3, 28, 14, 0, 261, 262, 5, 112, 0, 0, 262, 264, 3, 28, 14, 0, 263, 261, 1, 0, 0, 0, 264, 267, 1, 0, 0, 0, 265, 263, 1, 0, 0, 0, 265, 266, 1, 0, 0, 0, 266, 31, 1, 0, 0, 0, 267, 265, 1, 0, 0, 0, 268, 272, 3, 34, 17, 0, 269, 272, 3, 38, 19, 0, 270, 272, 3, 114, 57, 0, 271, 268, 1, 0, 0, 0, 271, 269, 1, 0, 0, 0, 271, 270, 1, 0, 0, 0, 272, 273, 1, 0, 0, 0, 273, 274, 5, 0, 0, 1, 274, 33, 1, 0, 0, 0, 275, 281, 3, 36, 18, 0, 276, 277, 5, 91, 0, 0, 277, 278, 5, 1, 0, 0, 278, 280, 3, 36, 18, 0, 279, 276, 1, 0, 0, 0, 280, 283, 1, 0, 0, 0, 281, 279, 1, 0, 0, 0, 281, 282, 1, 0, 0, 0, 282, 35, 1, 0, 0, 0, 283, 281, 1, 0, 0, 0, 284, 291, 3, 38, 19, 0, 285, 286, 5, 126, 0, 0, 286, 287, 3, 34, 17, 0, 287, 288, 5, 144, 0, 0, 288, 291, 1, 0, 0, 0, 289, 291, 3, 154, 77, 0, 290, 284, 1, 0, 0, 0, 290, 285, 1, 0, 0, 0, 290, 289, 1, 0, 0, 0, 291, 37, 1, 0, 0, 0, 292, 294, 3, 40, 20, 0, 293, 292, 1, 0, 0, 0, 293, 294, 1, 0, 0, 0, 294, 295, 1, 0, 0, 0, 295, 297, 5, 77, 0, 0, 296, 298, 5, 23, 0, 0, 297, 296, 1, 0, 0, 0, 297, 298, 1, 0, 0, 0, 298, 300, 1, 0, 0, 0, 299, 301, 3, 42, 21, 0, 300, 299, 1, 0, 0, 0, 300, 301, 1, 0, 0, 0, 301, 302, 1, 0, 0, 0, 302, 304, 3, 104, 52, 0, 303, 305, 3, 44, 22, 0, 304, 303, 1, 0, 0, 0, 304, 305, 1, 0, 0, 0, 305, 307, 1, 0, 0, 0, 306, 308, 3, 46, 23, 0, 307, 306, 1, 0, 0, 0, 307, 308, 1, 0, 0, 0, 308, 310, 1, 0, 0, 0, 309, 311, 3, 50, 25, 0, 310, 309, 1, 0, 0, 0, 310, 311, 1, 0, 0, 0, 311, 313, 1, 0, 0, 0, 312, 314, 3, 52, 26, 0, 313, 312, 1, 0, 0, 0, 313, 314, 1, 0, 0, 0, 314, 316, 1, 0, 0, 0, 315, 317, 3, 54, 27, 0, 316, 315, 1, 0, 0, 0, 316, 317, 1, 0, 0, 0, 317, 320, 1, 0, 0, 0, 318, 319, 5, 98, 0, 0, 319, 321, 7, 0, 0, 0, 320, 318, 1, 0, 0, 0, 320, 321, 1, 0, 0, 0, 321, 324, 1, 0, 0, 0, 322, 323, 5, 98, 0, 0, 323, 325, 5, 86, 0, 0, 324, 322, 1, 0, 0, 0, 324, 325, 1, 0, 0, 0, 325, 327, 1, 0, 0, 0, 326, 328, 3, 56, 28, 0, 327, 326, 1, 0, 0, 0, 327, 328, 1, 0, 0, 0, 328, 330, 1, 0, 0, 0, 329, 331, 3, 48, 24, 0, 330, 329, 1, 0, 0, 0, 330, 331, 1, 0, 0, 0, 331, 333, 1, 0, 0, 0, 332, 334, 3, 58, 29, 0, 333, 332, 1, 0, 0, 0, 333, 334, 1, 0, 0, 0, 334, 337, 1, 0, 0, 0, 335, 338, 3, 62, 31, 0, 336, 338, 3, 64, 32, 0, 337, 335, 1, 0, 0, 0, 337, 336, 1, 0, 0, 0, 337, 338, 1, 0, 0, 0, 338, 340, 1, 0, 0, 0, 339, 341, 3, 66, 33, 0, 340, 339, 1, 0, 0, 0, 340, 341, 1, 0, 0, 0, 341, 39, 1, 0, 0, 0, 342, 343, 5, 98, 0, 0, 343, 344, 3, 118, 59, 0, 344, 41, 1, 0, 0, 0, 345, 346, 5, 85, 0, 0, 346, 349, 5, 104, 0, 0, 347, 348, 5, 98, 0, 0, 348, 350, 5, 82, 0, 0, 349, 347, 1, 0, 0, 0, 349, 350, 1, 0, 0, 0, 350, 43, 1, 0, 0, 0, 351, 352, 5, 32, 0, 0, 352, 353, 3, 68, 34, 0, 353, 45, 1, 0, 0, 0, 354, 356, 7, 1, 0, 0, 355, 354, 1, 0, 0, 0, 355, 356, 1, 0, 0, 0, 356, 357, 1, 0, 0, 0, 357, 358, 5, 5, 0, 0, 358, 359, 5, 45, 0, 0, 359, 360, 3, 104, 52, 0, 360, 47, 1, 0, 0, 0, 361, 362, 5, 97, 0, 0, 362, 363, 3, 150, 75, 0, 363, 364, 5, 6, 0, 0, 364, 365, 5, 126, 0, 0, 365, 366, 3, 88, 44, 0, 366, 376, 5, 144, 0, 0, 367, 368, 5, 112, 0, 0, 368, 369, 3, 150, 75, 0, 369, 370, 5, 6, 0, 0, 370, 371, 5, 126, 0, 0, 371, 372, 3, 88, 44, 0, 372, 373, 5, 144, 0, 0, 373, 375, 1, 0, 0, 0, 374, 367, 1, 0, 0, 0, 375, 378, 1, 0, 0, 0, 376, 374, 1, 0, 0, 0, 376, 377, 1, 0, 0, 0, 377, 49, 1, 0, 0, 0, 378, 376, 1, 0, 0, 0, 379, 380, 5, 67, 0, 0, 380, 381, 3, 106, 53, 0, 381, 51, 1, 0, 0, 0, 382, 383, 5, 95, 0, 0, 383, 384, 3, 106, 53, 0, 384, 53, 1, 0, 0, 0, 385, 386, 5, 34, 0, 0, 386, 393, 5, 11, 0, 0, 387, 388, 7, 0, 0, 0, 388, 389, 5, 126, 0, 0, 389, 390, 3, 104, 52, 0, 390, 391, 5, 144, 0, 0, 391, 394, 1, 0, 0, 0, 392, 394, 3, 104, 52, 0, 393, 387, 1, 0, 0, 0, 393, 392, 1, 0, 0, 0, 394, 55, 1, 0, 0, 0, 395, 396, 5, 35, 0, 0, 396, 397, 3, 106, 53, 0, 397, 57, 1, 0, 0, 0, 398, 399, 5, 62, 0, 0, 399, 400, 5, 11, 0, 0, 400, 401, 3, 78, 39, 0, 401, 59, 1, 0, 0, 0, 402, 403, 5, 62, 0, 0, 403, 404, 5, 11, 0, 0, 404, 405, 3, 104, 52, 0, 405, 61, 1, 0, 0, 0, 406, 407, 5, 52, 0, 0, 407, 410, 3, 106, 53, 0, 408, 409, 5, 112, 0, 0, 409, 411, 3, 106, 53, 0, 410, 408, 1, 0, 0, 0, 410, 411, 1, 0, 0, 0, 411, 416, 1, 0, 0, 0, 412, 413, 5, 98, 0, 0, 413, 417, 5, 82, 0, 0, 414, 415, 5, 11, 0, 0, 415, 417, 3, 104, 52, 0, 416, 412, 1, 0, 0, 0, 416, 414, 1, 0, 0, 0, 416, 417, 1, 0, 0, 0, 417, 436, 1, 0, 0, 0, 418, 419, 5, 52, 0, 0, 419, 422, 3, 106, 53, 0, 420, 421, 5, 98, 0, 0, 421, 423, 5, 82, 0, 0, 422, 420, 1, 0, 0, 0, 422, 423, 1, 0, 0, 0, 423, 424, 1, 0, 0, 0, 424, 425, 5, 59, 0, 0, 425, 426, 3, 106, 53, 0, 426, 436, 1, 0, 0, 0, 427, 428, 5, 52, 0, 0, 428, 429, 3, 106, 53, 0, 429, 430, 5, 59, 0, 0, 430, 433, 3, 106, 53, 0, 431, 432, 5, 11, 0, 0, 432, 434, 3, 104, 52, 0, 433, 431, 1, 0, 0, 0, 433, 434, 1, 0, 0, 0, 434, 436, 1, 0, 0, 0, 435, 406, 1, 0, 0, 0, 435, 418, 1, 0, 0, 0, 435, 427, 1, 0, 0, 0, 436, 63, 1, 0, 0, 0, 437, 438, 5, 59, 0, 0, 438, 439, 3, 106, 53, 0, 439, 65, 1, 0, 0, 0, 440, 441, 5, 79, 0, 0, 441, 442, 3, 84, 42, 0, 442, 67, 1, 0, 0, 0, 443, 444, 6, 34, -1, 0, 444, 446, 3, 126, 63, 0, 445, 447, 5, 27, 0, 0, 446, 445, 1, 0, 0, 0, 446, 447, 1, 0, 0, 0, 447, 449, 1, 0, 0, 0, 448, 450, 3, 76, 38, 0, 449, 448, 1, 0, 0, 0, 449, 450, 1, 0, 0, 0, 450, 456, 1, 0, 0, 0, 451, 452, 5, 126, 0, 0, 452, 453, 3, 68, 34, 0, 453, 454, 5, 144, 0, 0, 454, 456, 1, 0, 0, 0, 455, 443, 1, 0, 0, 0, 455, 451, 1, 0, 0, 0, 456, 471, 1, 0, 0, 0, 457, 458, 10, 3, 0, 0, 458, 459, 3, 72, 36, 0, 459, 460, 3, 68, 34, 4, 460, 470, 1, 0, 0, 0, 461, 463, 10, 4, 0, 0, 462, 464, 3, 70, 35, 0, 463, 462, 1, 0, 0, 0, 463, 464, 1, 0, 0, 0, 464, 465, 1, 0, 0, 0, 465, 466, 5, 45, 0, 0, 466, 467, 3, 68, 34, 0, 467, 468, 3, 74, 37, 0, 468, 470, 1, 0, 0, 0, 469, 457, 1, 0, 0, 0, 469, 461, 1, 0, 0, 0, 470, 473, 1, 0, 0, 0, 471, 469, 1, 0, 0, 0, 471, 472, 1, 0, 0, 0, 472, 69, 1, 0, 0, 0, 473, 471, 1, 0, 0, 0, 474, 476, 7, 2, 0, 0, 475, 474, 1, 0, 0, 0, 475, 476, 1, 0, 0, 0, 476, 477, 1, 0, 0, 0, 477, 484, 5, 42, 0, 0, 478, 480, 5, 42, 0, 0, 479, 481, 7, 2, 0, 0, 480, 479, 1, 0, 0, 0, 480, 481, 1, 0, 0, 0, 481, 484, 1, 0, 0, 0, 482, 484, 7, 2, 0, 0, 483, 475, 1, 0, 0, 0, 483, 478, 1, 0, 0, 0, 483, 482, 1, 0, 0, 0, 484, 518, 1, 0, 0, 0, 485, 487, 7, 3, 0, 0, 486, 485, 1, 0, 0, 0, 486, 487, 1, 0, 0, 0, 487, 488, 1, 0, 0, 0, 488, 490, 7, 4, 0, 0, 489, 491, 5, 63, 0, 0, 490, 489, 1, 0, 0, 0, 490, 491, 1, 0, 0, 0, 491, 500, 1, 0, 0, 0, 492, 494, 7, 4, 0, 0, 493, 495, 5, 63, 0, 0, 494, 493, 1, 0, 0, 0, 494, 495, 1, 0, 0, 0, 495, 497, 1, 0, 0, 0, 496, 498, 7, 3, 0, 0, 497, 496, 1, 0, 0, 0, 497, 498, 1, 0, 0, 0, 498, 500, 1, 0, 0, 0, 499, 486, 1, 0, 0, 0, 499, 492, 1, 0, 0, 0, 500, 518, 1, 0, 0, 0, 501, 503, 7, 5, 0, 0, 502, 501, 1, 0, 0, 0, 502, 503, 1, 0, 0, 0, 503, 504, 1, 0, 0, 0, 504, 506, 5, 33, 0, 0, 505, 507, 5, 63, 0, 0, 506, 505, 1, 0, 0, 0, 506, 507, 1, 0, 0, 0, 507, 516, 1, 0, 0, 0, 508, 510, 5, 33, 0, 0, 509, 511, 5, 63, 0, 0, 510, 509, 1, 0, 0, 0, 510, 511, 1, 0, 0, 0, 511, 513, 1, 0, 0, 0, 512, 514, 7, 5, 0, 0, 513, 512, 1, 0, 0, 0, 513, 514, 1, 0, 0, 0, 514, 516, 1, 0, 0, 0, 515, 502, 1, 0, 0, 0, 515, 508, 1, 0, 0, 0, 516, 518, 1, 0, 0, 0, 517, 483, 1, 0, 0, 0, 517, 499, 1, 0, 0, 0, 517, 515, 1, 0, 0, 0, 518, 71, 1, 0, 0, 0, 519, 520, 5, 16, 0, 0, 520, 523, 5, 45, 0, 0, 521, 523, 5, 112, 0, 0, 522, 519, 1, 0, 0, 0, 522, 521, 1, 0, 0, 0, 523, 73, 1, 0, 0, 0, 524, 525, 5, 60, 0, 0, 525, 534, 3, 104, 52, 0, 526, 527, 5, 92, 0, 0, 527, 528, 5, 126, 0, 0, 528, 529, 3, 104, 52, 0, 529, 530, 5, 144, 0, 0, 530, 534, 1, 0, 0, 0, 531, 532, 5, 92, 0, 0, 532, 534, 3, 104, 52, 0, 533, 524, 1, 0, 0, 0, 533, 526, 1, 0, 0, 0, 533, 531, 1, 0, 0, 0, 534, 75, 1, 0, 0, 0, 535, 536, 5, 75, 0, 0, 536, 539, 3, 82, 41, 0, 537, 538, 5, 59, 0, 0, 538, 540, 3, 82, 41, 0, 539, 537, 1, 0, 0, 0, 539, 540, 1, 0, 0, 0, 540, 77, 1, 0, 0, 0, 541, 546, 3, 80, 40, 0, 542, 543, 5, 112, 0, 0, 543, 545, 3, 80, 40, 0, 544, 542, 1, 0, 0, 0, 545, 548, 1, 0, 0, 0, 546, 544, 1, 0, 0, 0, 546, 547, 1, 0, 0, 0, 547, 79, 1, 0, 0, 0, 548, 546, 1, 0, 0, 0, 549, 551, 3, 106, 53, 0, 550, 552, 7, 6, 0, 0, 551, 550, 1, 0, 0, 0, 551, 552, 1, 0, 0, 0, 552, 555, 1, 0, 0, 0, 553, 554, 5, 58, 0, 0, 554, 556, 7, 7, 0, 0, 555, 553, 1, 0, 0, 0, 555, 556, 1, 0, 0, 0, 556, 559, 1, 0, 0, 0, 557, 558, 5, 15, 0, 0, 558, 560, 5, 106, 0, 0, 559, 557, 1, 0, 0, 0, 559, 560, 1, 0, 0, 0, 560, 81, 1, 0, 0, 0, 561, 568, 3, 154, 77, 0, 562, 565, 3, 138, 69, 0, 563, 564, 5, 146, 0, 0, 564, 566, 3, 138, 69, 0, 565, 563, 1, 0, 0, 0, 565, 566, 1, 0, 0, 0, 566, 568, 1, 0, 0, 0, 567, 561, 1, 0, 0, 0, 567, 562, 1, 0, 0, 0, 568, 83, 1, 0, 0, 0, 569, 574, 3, 86, 43, 0, 570, 571, 5, 112, 0, 0, 571, 573, 3, 86, 43, 0, 572, 570, 1, 0, 0, 0, 573, 576, 1, 0, 0, 0, 574, 572, 1, 0, 0, 0, 574, 575, 1, 0, 0, 0, 575, 85, 1, 0, 0, 0, 576, 574, 1, 0, 0, 0, 577, 578, 3, 150, 75, 0, 578, 579, 5, 118, 0, 0, 579, 580, 3, 140, 70, 0, 580, 87, 1, 0, 0, 0, 581, 583, 3, 90, 45, 0, 582, 581, 1, 0, 0, 0, 582, 583, 1, 0, 0, 0, 583, 585, 1, 0, 0, 0, 584, 586, 3, 92, 46, 0, 585, 584, 1, 0, 0, 0, 585, 586, 1, 0, 0, 0, 586, 588, 1, 0, 0, 0, 587, 589, 3, 94, 47, 0, 588, 587, 1, 0, 0, 0, 588, 589, 1, 0, 0, 0, 589, 89, 1, 0, 0, 0, 590, 591, 5, 65, 0, 0, 591, 592, 5, 11, 0, 0, 592, 593, 3, 104, 52, 0, 593, 91, 1, 0, 0, 0, 594, 595, 5, 62, 0, 0, 595, 596, 5, 11, 0, 0, 596, 597, 3, 78, 39, 0, 597, 93, 1, 0, 0, 0, 598, 599, 7, 8, 0, 0, 599, 600, 3, 96, 48, 0, 600, 95, 1, 0, 0, 0, 601, 608, 3, 98, 49, 0, 602, 603, 5, 9, 0, 0, 603, 604, 3, 98, 49, 0, 604, 605, 5, 2, 0, 0, 605, 606, 3, 98, 49, 0, 606, 608, 1, 0, 0, 0, 607, 601, 1, 0, 0, 0, 607, 602, 1, 0, 0, 0, 608, 97, 1, 0, 0, 0, 609, 610, 5, 18, 0, 0, 610, 622, 5, 73, 0, 0, 611, 612, 5, 90, 0, 0, 612, 622, 5, 66, 0, 0, 613, 614, 5, 90, 0, 0, 614, 622, 5, 30, 0, 0, 615, 616, 3, 138, 69, 0, 616, 617, 5, 66, 0, 0, 617, 622, 1, 0, 0, 0, 618, 619, 3, 138, 69, 0, 619, 620, 5, 30, 0, 0, 620, 622, 1, 0, 0, 0, 621, 609, 1, 0, 0, 0, 621, 611, 1, 0, 0, 0, 621, 613, 1, 0, 0, 0, 621, 615, 1, 0, 0, 0, 621, 618, 1, 0, 0, 0, 622, 99, 1, 0, 0, 0, 623, 624, 3, 106, 53, 0, 624, 625, 5, 0, 0, 1, 625, 101, 1, 0, 0, 0, 626, 674, 3, 150, 75, 0, 627, 628, 3, 150, 75, 0, 628, 629, 5, 126, 0, 0, 629, 630, 3, 150, 75, 0, 630, 637, 3, 102, 51, 0, 631, 632, 5, 112, 0, 0, 632, 633, 3, 150, 75, 0, 633, 634, 3, 102, 51, 0, 634, 636, 1, 0, 0, 0, 635, 631, 1, 0, 0, 0, 636, 639, 1, 0, 0, 0, 637, 635, 1, 0, 0, 0, 637, 638, 1, 0, 0, 0, 638, 640, 1, 0, 0, 0, 639, 637, 1, 0, 0, 0, 640, 641, 5, 144, 0, 0, 641, 674, 1, 0, 0, 0, 642, 643, 3, 150, 75, 0, 643, 644, 5, 126, 0, 0, 644, 649, 3, 152, 76, 0, 645, 646, 5, 112, 0, 0, 646, 648, 3, 152, 76, 0, 647, 645, 1, 0, 0, 0, 648, 651, 1, 0, 0, 0, 649, 647, 1, 0, 0, 0, 649, 650, 1, 0, 0, 0, 650, 652, 1, 0, 0, 0, 651, 649, 1, 0, 0, 0, 652, 653, 5, 144, 0, 0, 653, 674, 1, 0, 0, 0, 654, 655, 3, 150, 75, 0, 655, 656, 5, 126, 0, 0, 656, 661, 3, 102, 51, 0, 657, 658, 5, 112, 0, 0, 658, 660, 3, 102, 51, 0, 659, 657, 1, 0, 0, 0, 660, 663, 1, 0, 0, 0, 661, 659, 1, 0, 0, 0, 661, 662, 1, 0, 0, 0, 662, 664, 1, 0, 0, 0, 663, 661, 1, 0, 0, 0, 664, 665, 5, 144, 0, 0, 665, 674, 1, 0, 0, 0, 666, 667, 3, 150, 75, 0, 667, 669, 5, 126, 0, 0, 668, 670, 3, 104, 52, 0, 669, 668, 1, 0, 0, 0, 669, 670, 1, 0, 0, 0, 670, 671, 1, 0, 0, 0, 671, 672, 5, 144, 0, 0, 672, 674, 1, 0, 0, 0, 673, 626, 1, 0, 0, 0, 673, 627, 1, 0, 0, 0, 673, 642, 1, 0, 0, 0, 673, 654, 1, 0, 0, 0, 673, 666, 1, 0, 0, 0, 674, 103, 1, 0, 0, 0, 675, 680, 3, 106, 53, 0, 676, 677, 5, 112, 0, 0, 677, 679, 3, 106, 53, 0, 678, 676, 1, 0, 0, 0, 679, 682, 1, 0, 0, 0, 680, 678, 1, 0, 0, 0, 680, 681, 1, 0, 0, 0, 681, 105, 1, 0, 0, 0, 682, 680, 1, 0, 0, 0, 683, 684, 6, 53, -1, 0, 684, 686, 5, 12, 0, 0, 685, 687, 3, 106, 53, 0, 686, 685, 1, 0, 0, 0, 686, 687, 1, 0, 0, 0, 687, 693, 1, 0, 0, 0, 688, 689, 5, 94, 0, 0, 689, 690, 3, 106, 53, 0, 690, 691, 5, 81, 0, 0, 691, 692, 3, 106, 53, 0, 692, 694, 1, 0, 0, 0, 693, 688, 1, 0, 0, 0, 694, 695, 1, 0, 0, 0, 695, 693, 1, 0, 0, 0, 695, 696, 1, 0, 0, 0, 696, 699, 1, 0, 0, 0, 697, 698, 5, 24, 0, 0, 698, 700, 3, 106, 53, 0, 699, 697, 1, 0, 0, 0, 699, 700, 1, 0, 0, 0, 700, 701, 1, 0, 0, 0, 701, 702, 5, 25, 0, 0, 702, 813, 1, 0, 0, 0, 703, 704, 5, 13, 0, 0, 704, 705, 5, 126, 0, 0, 705, 706, 3, 106, 53, 0, 706, 707, 5, 6, 0, 0, 707, 708, 3, 102, 51, 0, 708, 709, 5, 144, 0, 0, 709, 813, 1, 0, 0, 0, 710, 711, 5, 19, 0, 0, 711, 813, 5, 106, 0, 0, 712, 713, 5, 43, 0, 0, 713, 714, 3, 106, 53, 0, 714, 715, 3, 142, 71, 0, 715, 813, 1, 0, 0, 0, 716, 717, 5, 80, 0, 0, 717, 718, 5, 126, 0, 0, 718, 719, 3, 106, 53, 0, 719, 720, 5, 32, 0, 0, 720, 723, 3, 106, 53, 0, 721, 722, 5, 31, 0, 0, 722, 724, 3, 106, 53, 0, 723, 721, 1, 0, 0, 0, 723, 724, 1, 0, 0, 0, 724, 725, 1, 0, 0, 0, 725, 726, 5, 144, 0, 0, 726, 813, 1, 0, 0, 0, 727, 728, 5, 83, 0, 0, 728, 813, 5, 106, 0, 0, 729, 730, 5, 88, 0, 0, 730, 731, 5, 126, 0, 0, 731, 732, 7, 9, 0, 0, 732, 733, 3, 156, 78, 0, 733, 734, 5, 32, 0, 0, 734, 735, 3, 106, 53, 0, 735, 736, 5, 144, 0, 0, 736, 813, 1, 0, 0, 0, 737, 738, 3, 150, 75, 0, 738, 740, 5, 126, 0, 0, 739, 741, 3, 104, 52, 0, 740, 739, 1, 0, 0, 0, 740, 741, 1, 0, 0, 0, 741, 742, 1, 0, 0, 0, 742, 743, 5, 144, 0, 0, 743, 744, 1, 0, 0, 0, 744, 745, 5, 64, 0, 0, 745, 746, 5, 126, 0, 0, 746, 747, 3, 88, 44, 0, 747, 748, 5, 144, 0, 0, 748, 813, 1, 0, 0, 0, 749, 750, 3, 150, 75, 0, 750, 752, 5, 126, 0, 0, 751, 753, 3, 104, 52, 0, 752, 751, 1, 0, 0, 0, 752, 753, 1, 0, 0, 0, 753, 754, 1, 0, 0, 0, 754, 755, 5, 144, 0, 0, 755, 756, 1, 0, 0, 0, 756, 757, 5, 64, 0, 0, 757, 758, 3, 150, 75, 0, 758, 813, 1, 0, 0, 0, 759, 765, 3, 150, 75, 0, 760, 762, 5, 126, 0, 0, 761, 763, 3, 104, 52, 0, 762, 761, 1, 0, 0, 0, 762, 763, 1, 0, 0, 0, 763, 764, 1, 0, 0, 0, 764, 766, 5, 144, 0, 0, 765, 760, 1, 0, 0, 0, 765, 766, 1, 0, 0, 0, 766, 767, 1, 0, 0, 0, 767, 769, 5, 126, 0, 0, 768, 770, 5, 23, 0, 0, 769, 768, 1, 0, 0, 0, 769, 770, 1, 0, 0, 0, 770, 772, 1, 0, 0, 0, 771, 773, 3, 108, 54, 0, 772, 771, 1, 0, 0, 0, 772, 773, 1, 0, 0, 0, 773, 774, 1, 0, 0, 0, 774, 775, 5, 144, 0, 0, 775, 813, 1, 0, 0, 0, 776, 813, 3, 114, 57, 0, 777, 813, 3, 158, 79, 0, 778, 813, 3, 140, 70, 0, 779, 780, 5, 114, 0, 0, 780, 813, 3, 106, 53, 19, 781, 782, 5, 56, 0, 0, 782, 813, 3, 106, 53, 13, 783, 784, 3, 130, 65, 0, 784, 785, 5, 116, 0, 0, 785, 787, 1, 0, 0, 0, 786, 783, 1, 0, 0, 0, 786, 787, 1, 0, 0, 0, 787, 788, 1, 0, 0, 0, 788, 813, 5, 108, 0, 0, 789, 790, 5, 126, 0, 0, 790, 791, 3, 34, 17, 0, 791, 792, 5, 144, 0, 0, 792, 813, 1, 0, 0, 0, 793, 794, 5, 126, 0, 0, 794, 795, 3, 106, 53, 0, 795, 796, 5, 144, 0, 0, 796, 813, 1, 0, 0, 0, 797, 798, 5, 126, 0, 0, 798, 799, 3, 104, 52, 0, 799, 800, 5, 144, 0, 0, 800, 813, 1, 0, 0, 0, 801, 803, 5, 125, 0, 0, 802, 804, 3, 104, 52, 0, 803, 802, 1, 0, 0, 0, 803, 804, 1, 0, 0, 0, 804, 805, 1, 0, 0, 0, 805, 813, 5, 143, 0, 0, 806, 808, 5, 124, 0, 0, 807, 809, 3, 30, 15, 0, 808, 807, 1, 0, 0, 0, 808, 809, 1, 0, 0, 0, 809, 810, 1, 0, 0, 0, 810, 813, 5, 142, 0, 0, 811, 813, 3, 122, 61, 0, 812, 683, 1, 0, 0, 0, 812, 703, 1, 0, 0, 0, 812, 710, 1, 0, 0, 0, 812, 712, 1, 0, 0, 0, 812, 716, 1, 0, 0, 0, 812, 727, 1, 0, 0, 0, 812, 729, 1, 0, 0, 0, 812, 737, 1, 0, 0, 0, 812, 749, 1, 0, 0, 0, 812, 759, 1, 0, 0, 0, 812, 776, 1, 0, 0, 0, 812, 777, 1, 0, 0, 0, 812, 778, 1, 0, 0, 0, 812, 779, 1, 0, 0, 0, 812, 781, 1, 0, 0, 0, 812, 786, 1, 0, 0, 0, 812, 789, 1, 0, 0, 0, 812, 793, 1, 0, 0, 0, 812, 797, 1, 0, 0, 0, 812, 801, 1, 0, 0, 0, 812, 806, 1, 0, 0, 0, 812, 811, 1, 0, 0, 0, 813, 907, 1, 0, 0, 0, 814, 818, 10, 18, 0, 0, 815, 819, 5, 108, 0, 0, 816, 819, 5, 146, 0, 0, 817, 819, 5, 133, 0, 0, 818, 815, 1, 0, 0, 0, 818, 816, 1, 0, 0, 0, 818, 817, 1, 0, 0, 0, 819, 820, 1, 0, 0, 0, 820, 906, 3, 106, 53, 19, 821, 825, 10, 17, 0, 0, 822, 826, 5, 134, 0, 0, 823, 826, 5, 114, 0, 0, 824, 826, 5, 113, 0, 0, 825, 822, 1, 0, 0, 0, 825, 823, 1, 0, 0, 0, 825, 824, 1, 0, 0, 0, 826, 827, 1, 0, 0, 0, 827, 906, 3, 106, 53, 18, 828, 853, 10, 16, 0, 0, 829, 854, 5, 117, 0, 0, 830, 854, 5, 118, 0, 0, 831, 854, 5, 129, 0, 0, 832, 854, 5, 127, 0, 0, 833, 854, 5, 128, 0, 0, 834, 854, 5, 119, 0, 0, 835, 854, 5, 120, 0, 0, 836, 838, 5, 56, 0, 0, 837, 836, 1, 0, 0, 0, 837, 838, 1, 0, 0, 0, 838, 839, 1, 0, 0, 0, 839, 841, 5, 40, 0, 0, 840, 842, 5, 14, 0, 0, 841, 840, 1, 0, 0, 0, 841, 842, 1, 0, 0, 0, 842, 854, 1, 0, 0, 0, 843, 845, 5, 56, 0, 0, 844, 843, 1, 0, 0, 0, 844, 845, 1, 0, 0, 0, 845, 846, 1, 0, 0, 0, 846, 854, 7, 10, 0, 0, 847, 854, 5, 140, 0, 0, 848, 854, 5, 141, 0, 0, 849, 854, 5, 131, 0, 0, 850, 854, 5, 122, 0, 0, 851, 854, 5, 123, 0, 0, 852, 854, 5, 130, 0, 0, 853, 829, 1, 0, 0, 0, 853, 830, 1, 0, 0, 0, 853, 831, 1, 0, 0, 0, 853, 832, 1, 0, 0, 0, 853, 833, 1, 0, 0, 0, 853, 834, 1, 0, 0, 0, 853, 835, 1, 0, 0, 0, 853, 837, 1, 0, 0, 0, 853, 844, 1, 0, 0, 0, 853, 847, 1, 0, 0, 0, 853, 848, 1, 0, 0, 0, 853, 849, 1, 0, 0, 0, 853, 850, 1, 0, 0, 0, 853, 851, 1, 0, 0, 0, 853, 852, 1, 0, 0, 0, 854, 855, 1, 0, 0, 0, 855, 906, 3, 106, 53, 17, 856, 857, 10, 14, 0, 0, 857, 858, 5, 132, 0, 0, 858, 906, 3, 106, 53, 15, 859, 860, 10, 12, 0, 0, 860, 861, 5, 2, 0, 0, 861, 906, 3, 106, 53, 13, 862, 863, 10, 11, 0, 0, 863, 864, 5, 61, 0, 0, 864, 906, 3, 106, 53, 12, 865, 867, 10, 10, 0, 0, 866, 868, 5, 56, 0, 0, 867, 866, 1, 0, 0, 0, 867, 868, 1, 0, 0, 0, 868, 869, 1, 0, 0, 0, 869, 870, 5, 9, 0, 0, 870, 871, 3, 106, 53, 0, 871, 872, 5, 2, 0, 0, 872, 873, 3, 106, 53, 11, 873, 906, 1, 0, 0, 0, 874, 875, 10, 9, 0, 0, 875, 876, 5, 135, 0, 0, 876, 877, 3, 106, 53, 0, 877, 878, 5, 111, 0, 0, 878, 879, 3, 106, 53, 9, 879, 906, 1, 0, 0, 0, 880, 881, 10, 22, 0, 0, 881, 882, 5, 125, 0, 0, 882, 883, 3, 106, 53, 0, 883, 884, 5, 143, 0, 0, 884, 906, 1, 0, 0, 0, 885, 886, 10, 21, 0, 0, 886, 887, 5, 116, 0, 0, 887, 906, 5, 104, 0, 0, 888, 889, 10, 20, 0, 0, 889, 890, 5, 116, 0, 0, 890, 906, 3, 150, 75, 0, 891, 892, 10, 15, 0, 0, 892, 894, 5, 44, 0, 0, 893, 895, 5, 56, 0, 0, 894, 893, 1, 0, 0, 0, 894, 895, 1, 0, 0, 0, 895, 896, 1, 0, 0, 0, 896, 906, 5, 57, 0, 0, 897, 903, 10, 8, 0, 0, 898, 904, 3, 148, 74, 0, 899, 900, 5, 6, 0, 0, 900, 904, 3, 150, 75, 0, 901, 902, 5, 6, 0, 0, 902, 904, 5, 106, 0, 0, 903, 898, 1, 0, 0, 0, 903, 899, 1, 0, 0, 0, 903, 901, 1, 0, 0, 0, 904, 906, 1, 0, 0, 0, 905, 814, 1, 0, 0, 0, 905, 821, 1, 0, 0, 0, 905, 828, 1, 0, 0, 0, 905, 856, 1, 0, 0, 0, 905, 859, 1, 0, 0, 0, 905, 862, 1, 0, 0, 0, 905, 865, 1, 0, 0, 0, 905, 874, 1, 0, 0, 0, 905, 880, 1, 0, 0, 0, 905, 885, 1, 0, 0, 0, 905, 888, 1, 0, 0, 0, 905, 891, 1, 0, 0, 0, 905, 897, 1, 0, 0, 0, 906, 909, 1, 0, 0, 0, 907, 905, 1, 0, 0, 0, 907, 908, 1, 0, 0, 0, 908, 107, 1, 0, 0, 0, 909, 907, 1, 0, 0, 0, 910, 915, 3, 110, 55, 0, 911, 912, 5, 112, 0, 0, 912, 914, 3, 110, 55, 0, 913, 911, 1, 0, 0, 0, 914, 917, 1, 0, 0, 0, 915, 913, 1, 0, 0, 0, 915, 916, 1, 0, 0, 0, 916, 109, 1, 0, 0, 0, 917, 915, 1, 0, 0, 0, 918, 921, 3, 112, 56, 0, 919, 921, 3, 106, 53, 0, 920, 918, 1, 0, 0, 0, 920, 919, 1, 0, 0, 0, 921, 111, 1, 0, 0, 0, 922, 923, 5, 126, 0, 0, 923, 928, 3, 150, 75, 0, 924, 925, 5, 112, 0, 0, 925, 927, 3, 150, 75, 0, 926, 924, 1, 0, 0, 0, 927, 930, 1, 0, 0, 0, 928, 926, 1, 0, 0, 0, 928, 929, 1, 0, 0, 0, 929, 931, 1, 0, 0, 0, 930, 928, 1, 0, 0, 0, 931, 932, 5, 144, 0, 0, 932, 942, 1, 0, 0, 0, 933, 938, 3, 150, 75, 0, 934, 935, 5, 112, 0, 0, 935, 937, 3, 150, 75, 0, 936, 934, 1, 0, 0, 0, 937, 940, 1, 0, 0, 0, 938, 936, 1, 0, 0, 0, 938, 939, 1, 0, 0, 0, 939, 942, 1, 0, 0, 0, 940, 938, 1, 0, 0, 0, 941, 922, 1, 0, 0, 0, 941, 933, 1, 0, 0, 0, 942, 943, 1, 0, 0, 0, 943, 944, 5, 107, 0, 0, 944, 945, 3, 106, 53, 0, 945, 113, 1, 0, 0, 0, 946, 947, 5, 128, 0, 0, 947, 951, 3, 150, 75, 0, 948, 950, 3, 116, 58, 0, 949, 948, 1, 0, 0, 0, 950, 953, 1, 0, 0, 0, 951, 949, 1, 0, 0, 0, 951, 952, 1, 0, 0, 0, 952, 954, 1, 0, 0, 0, 953, 951, 1, 0, 0, 0, 954, 955, 5, 146, 0, 0, 955, 956, 5, 120, 0, 0, 956, 975, 1, 0, 0, 0, 957, 958, 5, 128, 0, 0, 958, 962, 3, 150, 75, 0, 959, 961, 3, 116, 58, 0, 960, 959, 1, 0, 0, 0, 961, 964, 1, 0, 0, 0, 962, 960, 1, 0, 0, 0, 962, 963, 1, 0, 0, 0, 963, 965, 1, 0, 0, 0, 964, 962, 1, 0, 0, 0, 965, 967, 5, 120, 0, 0, 966, 968, 3, 114, 57, 0, 967, 966, 1, 0, 0, 0, 967, 968, 1, 0, 0, 0, 968, 969, 1, 0, 0, 0, 969, 970, 5, 128, 0, 0, 970, 971, 5, 146, 0, 0, 971, 972, 3, 150, 75, 0, 972, 973, 5, 120, 0, 0, 973, 975, 1, 0, 0, 0, 974, 946, 1, 0, 0, 0, 974, 957, 1, 0, 0, 0, 975, 115, 1, 0, 0, 0, 976, 977, 3, 150, 75, 0, 977, 978, 5, 118, 0, 0, 978, 979, 3, 156, 78, 0, 979, 988, 1, 0, 0, 0, 980, 981, 3, 150, 75, 0, 981, 982, 5, 118, 0, 0, 982, 983, 5, 124, 0, 0, 983, 984, 3, 106, 53, 0, 984, 985, 5, 142, 0, 0, 985, 988, 1, 0, 0, 0, 986, 988, 3, 150, 75, 0, 987, 976, 1, 0, 0, 0, 987, 980, 1, 0, 0, 0, 987, 986, 1, 0, 0, 0, 988, 117, 1, 0, 0, 0, 989, 994, 3, 120, 60, 0, 990, 991, 5, 112, 0, 0, 991, 993, 3, 120, 60, 0, 992, 990, 1, 0, 0, 0, 993, 996, 1, 0, 0, 0, 994, 992, 1, 0, 0, 0, 994, 995, 1, 0, 0, 0, 995, 119, 1, 0, 0, 0, 996, 994, 1, 0, 0, 0, 997, 998, 3, 150, 75, 0, 998, 999, 5, 6, 0, 0, 999, 1000, 5, 126, 0, 0, 1000, 1001, 3, 34, 17, 0, 1001, 1002, 5, 144, 0, 0, 1002, 1008, 1, 0, 0, 0, 1003, 1004, 3, 106, 53, 0, 1004, 1005, 5, 6, 0, 0, 1005, 1006, 3, 150, 75, 0, 1006, 1008, 1, 0, 0, 0, 1007, 997, 1, 0, 0, 0, 1007, 1003, 1, 0, 0, 0, 1008, 121, 1, 0, 0, 0, 1009, 1017, 3, 154, 77, 0, 1010, 1011, 3, 130, 65, 0, 1011, 1012, 5, 116, 0, 0, 1012, 1014, 1, 0, 0, 0, 1013, 1010, 1, 0, 0, 0, 1013, 1014, 1, 0, 0, 0, 1014, 1015, 1, 0, 0, 0, 1015, 1017, 3, 124, 62, 0, 1016, 1009, 1, 0, 0, 0, 1016, 1013, 1, 0, 0, 0, 1017, 123, 1, 0, 0, 0, 1018, 1023, 3, 150, 75, 0, 1019, 1020, 5, 116, 0, 0, 1020, 1022, 3, 150, 75, 0, 1021, 1019, 1, 0, 0, 0, 1022, 1025, 1, 0, 0, 0, 1023, 1021, 1, 0, 0, 0, 1023, 1024, 1, 0, 0, 0, 1024, 125, 1, 0, 0, 0, 1025, 1023, 1, 0, 0, 0, 1026, 1027, 6, 63, -1, 0, 1027, 1036, 3, 130, 65, 0, 1028, 1036, 3, 128, 64, 0, 1029, 1030, 5, 126, 0, 0, 1030, 1031, 3, 34, 17, 0, 1031, 1032, 5, 144, 0, 0, 1032, 1036, 1, 0, 0, 0, 1033, 1036, 3, 114, 57, 0, 1034, 1036, 3, 154, 77, 0, 1035, 1026, 1, 0, 0, 0, 1035, 1028, 1, 0, 0, 0, 1035, 1029, 1, 0, 0, 0, 1035, 1033, 1, 0, 0, 0, 1035, 1034, 1, 0, 0, 0, 1036, 1045, 1, 0, 0, 0, 1037, 1041, 10, 3, 0, 0, 1038, 1042, 3, 148, 74, 0, 1039, 1040, 5, 6, 0, 0, 1040, 1042, 3, 150, 75, 0, 1041, 1038, 1, 0, 0, 0, 1041, 1039, 1, 0, 0, 0, 1042, 1044, 1, 0, 0, 0, 1043, 1037, 1, 0, 0, 0, 1044, 1047, 1, 0, 0, 0, 1045, 1043, 1, 0, 0, 0, 1045, 1046, 1, 0, 0, 0, 1046, 127, 1, 0, 0, 0, 1047, 1045, 1, 0, 0, 0, 1048, 1049, 3, 150, 75, 0, 1049, 1051, 5, 126, 0, 0, 1050, 1052, 3, 132, 66, 0, 1051, 1050, 1, 0, 0, 0, 1051, 1052, 1, 0, 0, 0, 1052, 1053, 1, 0, 0, 0, 1053, 1054, 5, 144, 0, 0, 1054, 129, 1, 0, 0, 0, 1055, 1056, 3, 134, 67, 0, 1056, 1057, 5, 116, 0, 0, 1057, 1059, 1, 0, 0, 0, 1058, 1055, 1, 0, 0, 0, 1058, 1059, 1, 0, 0, 0, 1059, 1060, 1, 0, 0, 0, 1060, 1061, 3, 150, 75, 0, 1061, 131, 1, 0, 0, 0, 1062, 1067, 3, 106, 53, 0, 1063, 1064, 5, 112, 0, 0, 1064, 1066, 3, 106, 53, 0, 1065, 1063, 1, 0, 0, 0, 1066, 1069, 1, 0, 0, 0, 1067, 1065, 1, 0, 0, 0, 1067, 1068, 1, 0, 0, 0, 1068, 133, 1, 0, 0, 0, 1069, 1067, 1, 0, 0, 0, 1070, 1071, 3, 150, 75, 0, 1071, 135, 1, 0, 0, 0, 1072, 1081, 5, 102, 0, 0, 1073, 1074, 5, 116, 0, 0, 1074, 1081, 7, 11, 0, 0, 1075, 1076, 5, 104, 0, 0, 1076, 1078, 5, 116, 0, 0, 1077, 1079, 7, 11, 0, 0, 1078, 1077, 1, 0, 0, 0, 1078, 1079, 1, 0, 0, 0, 1079, 1081, 1, 0, 0, 0, 1080, 1072, 1, 0, 0, 0, 1080, 1073, 1, 0, 0, 0, 1080, 1075, 1, 0, 0, 0, 1081, 137, 1, 0, 0, 0, 1082, 1084, 7, 12, 0, 0, 1083, 1082, 1, 0, 0, 0, 1083, 1084, 1, 0, 0, 0, 1084, 1091, 1, 0, 0, 0, 1085, 1092, 3, 136, 68, 0, 1086, 1092, 5, 103, 0, 0, 1087, 1092, 5, 104, 0, 0, 1088, 1092, 5, 105, 0, 0, 1089, 1092, 5, 41, 0, 0, 1090, 1092, 5, 55, 0, 0, 1091, 1085, 1, 0, 0, 0, 1091, 1086, 1, 0, 0, 0, 1091, 1087, 1, 0, 0, 0, 1091, 1088, 1, 0, 0, 0, 1091, 1089, 1, 0, 0, 0, 1091, 1090, 1, 0, 0, 0, 1092, 139, 1, 0, 0, 0, 1093, 1097, 3, 138, 69, 0, 1094, 1097, 5, 106, 0, 0, 1095, 1097, 5, 57, 0, 0, 1096, 1093, 1, 0, 0, 0, 1096, 1094, 1, 0, 0, 0, 1096, 1095, 1, 0, 0, 0, 1097, 141, 1, 0, 0, 0, 1098, 1099, 7, 13, 0, 0, 1099, 143, 1, 0, 0, 0, 1100, 1101, 7, 14, 0, 0, 1101, 145, 1, 0, 0, 0, 1102, 1103, 7, 15, 0, 0, 1103, 147, 1, 0, 0, 0, 1104, 1107, 5, 101, 0, 0, 1105, 1107, 3, 146, 73, 0, 1106, 1104, 1, 0, 0, 0, 1106, 1105, 1, 0, 0, 0, 1107, 149, 1, 0, 0, 0, 1108, 1112, 5, 101, 0, 0, 1109, 1112, 3, 142, 71, 0, 1110, 1112, 3, 144, 72, 0, 1111, 1108, 1, 0, 0, 0, 1111, 1109, 1, 0, 0, 0, 1111, 1110, 1, 0, 0, 0, 1112, 151, 1, 0, 0, 0, 1113, 1114, 3, 156, 78, 0, 1114, 1115, 5, 118, 0, 0, 1115, 1116, 3, 138, 69, 0, 1116, 153, 1, 0, 0, 0, 1117, 1118, 5, 124, 0, 0, 1118, 1119, 3, 150, 75, 0, 1119, 1120, 5, 142, 0, 0, 1120, 155, 1, 0, 0, 0, 1121, 1124, 5, 106, 0, 0, 1122, 1124, 3, 158, 79, 0, 1123, 1121, 1, 0, 0, 0, 1123, 1122, 1, 0, 0, 0, 1124, 157, 1, 0, 0, 0, 1125, 1129, 5, 137, 0, 0, 1126, 1128, 3, 160, 80, 0, 1127, 1126, 1, 0, 0, 0, 1128, 1131, 1, 0, 0, 0, 1129, 1127, 1, 0, 0, 0, 1129, 1130, 1, 0, 0, 0, 1130, 1132, 1, 0, 0, 0, 1131, 1129, 1, 0, 0, 0, 1132, 1133, 5, 139, 0, 0, 1133, 159, 1, 0, 0, 0, 1134, 1135, 5, 152, 0, 0, 1135, 1136, 3, 106, 53, 0, 1136, 1137, 5, 142, 0, 0, 1137, 1140, 1, 0, 0, 0, 1138, 1140, 5, 151, 0, 0, 1139, 1134, 1, 0, 0, 0, 1139, 1138, 1, 0, 0, 0, 1140, 161, 1, 0, 0, 0, 1141, 1145, 5, 138, 0, 0, 1142, 1144, 3, 164, 82, 0, 1143, 1142, 1, 0, 0, 0, 1144, 1147, 1, 0, 0, 0, 1145, 1143, 1, 0, 0, 0, 1145, 1146, 1, 0, 0, 0, 1146, 1148, 1, 0, 0, 0, 1147, 1145, 1, 0, 0, 0, 1148, 1149, 5, 0, 0, 1, 1149, 163, 1, 0, 0, 0, 1150, 1151, 5, 154, 0, 0, 1151, 1152, 3, 106, 53, 0, 1152, 1153, 5, 142, 0, 0, 1153, 1156, 1, 0, 0, 0, 1154, 1156, 5, 153, 0, 0, 1155, 1150, 1, 0, 0, 0, 1155, 1154, 1, 0, 0, 0, 1156, 165, 1, 0, 0, 0, 135, 169, 176, 185, 200, 212, 224, 240, 251, 265, 271, 281, 290, 293, 297, 300, 304, 307, 310, 313, 316, 320, 324, 327, 330, 333, 337, 340, 349, 355, 376, 393, 410, 416, 422, 433, 435, 446, 449, 455, 463, 469, 471, 475, 480, 483, 486, 490, 494, 497, 499, 502, 506, 510, 513, 515, 517, 522, 533, 539, 546, 551, 555, 559, 565, 567, 574, 582, 585, 588, 607, 621, 637, 649, 661, 669, 673, 680, 686, 695, 699, 723, 740, 752, 762, 765, 769, 772, 786, 803, 808, 812, 818, 825, 837, 841, 844, 853, 867, 894, 903, 905, 907, 915, 920, 928, 938, 941, 951, 962, 967, 974, 987, 994, 1007, 1013, 1016, 1023, 1035, 1041, 1045, 1051, 1058, 1067, 1078, 1080, 1083, 1091, 1096, 1106, 1111, 1123, 1129, 1139, 1145, 1155] \ No newline at end of file +[4, 1, 154, 1178, 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, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, 41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, 2, 47, 7, 47, 2, 48, 7, 48, 2, 49, 7, 49, 2, 50, 7, 50, 2, 51, 7, 51, 2, 52, 7, 52, 2, 53, 7, 53, 2, 54, 7, 54, 2, 55, 7, 55, 2, 56, 7, 56, 2, 57, 7, 57, 2, 58, 7, 58, 2, 59, 7, 59, 2, 60, 7, 60, 2, 61, 7, 61, 2, 62, 7, 62, 2, 63, 7, 63, 2, 64, 7, 64, 2, 65, 7, 65, 2, 66, 7, 66, 2, 67, 7, 67, 2, 68, 7, 68, 2, 69, 7, 69, 2, 70, 7, 70, 2, 71, 7, 71, 2, 72, 7, 72, 2, 73, 7, 73, 2, 74, 7, 74, 2, 75, 7, 75, 2, 76, 7, 76, 2, 77, 7, 77, 2, 78, 7, 78, 2, 79, 7, 79, 2, 80, 7, 80, 2, 81, 7, 81, 2, 82, 7, 82, 1, 0, 5, 0, 168, 8, 0, 10, 0, 12, 0, 171, 9, 0, 1, 0, 1, 0, 1, 1, 1, 1, 3, 1, 177, 8, 1, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 3, 3, 186, 8, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 5, 5, 199, 8, 5, 10, 5, 12, 5, 202, 9, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 3, 6, 213, 8, 6, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 3, 8, 225, 8, 8, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, 1, 11, 3, 11, 241, 8, 11, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 13, 1, 13, 5, 13, 250, 8, 13, 10, 13, 12, 13, 253, 9, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 1, 14, 1, 15, 1, 15, 1, 15, 5, 15, 264, 8, 15, 10, 15, 12, 15, 267, 9, 15, 1, 16, 1, 16, 1, 16, 3, 16, 272, 8, 16, 1, 16, 1, 16, 1, 17, 1, 17, 1, 17, 1, 17, 5, 17, 280, 8, 17, 10, 17, 12, 17, 283, 9, 17, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 3, 18, 291, 8, 18, 1, 19, 3, 19, 294, 8, 19, 1, 19, 1, 19, 3, 19, 298, 8, 19, 1, 19, 3, 19, 301, 8, 19, 1, 19, 1, 19, 3, 19, 305, 8, 19, 1, 19, 3, 19, 308, 8, 19, 1, 19, 3, 19, 311, 8, 19, 1, 19, 3, 19, 314, 8, 19, 1, 19, 3, 19, 317, 8, 19, 1, 19, 1, 19, 3, 19, 321, 8, 19, 1, 19, 1, 19, 3, 19, 325, 8, 19, 1, 19, 3, 19, 328, 8, 19, 1, 19, 3, 19, 331, 8, 19, 1, 19, 3, 19, 334, 8, 19, 1, 19, 1, 19, 3, 19, 338, 8, 19, 1, 19, 3, 19, 341, 8, 19, 1, 20, 1, 20, 1, 20, 1, 21, 1, 21, 1, 21, 1, 21, 3, 21, 350, 8, 21, 1, 22, 1, 22, 1, 22, 1, 23, 3, 23, 356, 8, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 5, 24, 375, 8, 24, 10, 24, 12, 24, 378, 9, 24, 1, 25, 1, 25, 1, 25, 1, 26, 1, 26, 1, 26, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 3, 27, 394, 8, 27, 1, 28, 1, 28, 1, 28, 1, 29, 1, 29, 1, 29, 1, 29, 1, 30, 1, 30, 1, 30, 1, 30, 1, 31, 1, 31, 1, 31, 1, 31, 3, 31, 411, 8, 31, 1, 31, 1, 31, 1, 31, 1, 31, 3, 31, 417, 8, 31, 1, 31, 1, 31, 1, 31, 1, 31, 3, 31, 423, 8, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 3, 31, 434, 8, 31, 3, 31, 436, 8, 31, 1, 32, 1, 32, 1, 32, 1, 33, 1, 33, 1, 33, 1, 34, 1, 34, 1, 34, 3, 34, 447, 8, 34, 1, 34, 3, 34, 450, 8, 34, 1, 34, 1, 34, 1, 34, 1, 34, 3, 34, 456, 8, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 3, 34, 464, 8, 34, 1, 34, 1, 34, 1, 34, 1, 34, 5, 34, 470, 8, 34, 10, 34, 12, 34, 473, 9, 34, 1, 35, 3, 35, 476, 8, 35, 1, 35, 1, 35, 1, 35, 3, 35, 481, 8, 35, 1, 35, 3, 35, 484, 8, 35, 1, 35, 3, 35, 487, 8, 35, 1, 35, 1, 35, 3, 35, 491, 8, 35, 1, 35, 1, 35, 3, 35, 495, 8, 35, 1, 35, 3, 35, 498, 8, 35, 3, 35, 500, 8, 35, 1, 35, 3, 35, 503, 8, 35, 1, 35, 1, 35, 3, 35, 507, 8, 35, 1, 35, 1, 35, 3, 35, 511, 8, 35, 1, 35, 3, 35, 514, 8, 35, 3, 35, 516, 8, 35, 3, 35, 518, 8, 35, 1, 36, 1, 36, 1, 36, 3, 36, 523, 8, 36, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 3, 37, 534, 8, 37, 1, 38, 1, 38, 1, 38, 1, 38, 3, 38, 540, 8, 38, 1, 39, 1, 39, 1, 39, 5, 39, 545, 8, 39, 10, 39, 12, 39, 548, 9, 39, 1, 40, 1, 40, 3, 40, 552, 8, 40, 1, 40, 1, 40, 3, 40, 556, 8, 40, 1, 40, 1, 40, 3, 40, 560, 8, 40, 1, 41, 1, 41, 1, 41, 1, 41, 3, 41, 566, 8, 41, 3, 41, 568, 8, 41, 1, 42, 1, 42, 1, 42, 5, 42, 573, 8, 42, 10, 42, 12, 42, 576, 9, 42, 1, 43, 1, 43, 1, 43, 1, 43, 1, 44, 3, 44, 583, 8, 44, 1, 44, 3, 44, 586, 8, 44, 1, 44, 3, 44, 589, 8, 44, 1, 45, 1, 45, 1, 45, 1, 45, 1, 46, 1, 46, 1, 46, 1, 46, 1, 47, 1, 47, 1, 47, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 3, 48, 608, 8, 48, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 3, 49, 622, 8, 49, 1, 50, 1, 50, 1, 50, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 5, 51, 636, 8, 51, 10, 51, 12, 51, 639, 9, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 5, 51, 648, 8, 51, 10, 51, 12, 51, 651, 9, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 5, 51, 660, 8, 51, 10, 51, 12, 51, 663, 9, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 3, 51, 670, 8, 51, 1, 51, 1, 51, 3, 51, 674, 8, 51, 1, 52, 1, 52, 1, 52, 5, 52, 679, 8, 52, 10, 52, 12, 52, 682, 9, 52, 1, 53, 1, 53, 1, 53, 3, 53, 687, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 4, 53, 694, 8, 53, 11, 53, 12, 53, 695, 1, 53, 1, 53, 3, 53, 700, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 724, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 741, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 747, 8, 53, 1, 53, 3, 53, 750, 8, 53, 1, 53, 3, 53, 753, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 763, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 769, 8, 53, 1, 53, 3, 53, 772, 8, 53, 1, 53, 3, 53, 775, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 783, 8, 53, 1, 53, 3, 53, 786, 8, 53, 1, 53, 1, 53, 3, 53, 790, 8, 53, 1, 53, 3, 53, 793, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 807, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 824, 8, 53, 1, 53, 1, 53, 1, 53, 3, 53, 829, 8, 53, 1, 53, 1, 53, 3, 53, 833, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 839, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 846, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 858, 8, 53, 1, 53, 1, 53, 3, 53, 862, 8, 53, 1, 53, 3, 53, 865, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 874, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 888, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 915, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 924, 8, 53, 5, 53, 926, 8, 53, 10, 53, 12, 53, 929, 9, 53, 1, 54, 1, 54, 1, 54, 5, 54, 934, 8, 54, 10, 54, 12, 54, 937, 9, 54, 1, 55, 1, 55, 3, 55, 941, 8, 55, 1, 56, 1, 56, 1, 56, 1, 56, 5, 56, 947, 8, 56, 10, 56, 12, 56, 950, 9, 56, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 5, 56, 957, 8, 56, 10, 56, 12, 56, 960, 9, 56, 3, 56, 962, 8, 56, 1, 56, 1, 56, 1, 56, 1, 57, 1, 57, 1, 57, 5, 57, 970, 8, 57, 10, 57, 12, 57, 973, 9, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 5, 57, 981, 8, 57, 10, 57, 12, 57, 984, 9, 57, 1, 57, 1, 57, 3, 57, 988, 8, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 3, 57, 995, 8, 57, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 3, 58, 1008, 8, 58, 1, 59, 1, 59, 1, 59, 5, 59, 1013, 8, 59, 10, 59, 12, 59, 1016, 9, 59, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 3, 60, 1028, 8, 60, 1, 61, 1, 61, 1, 61, 1, 61, 3, 61, 1034, 8, 61, 1, 61, 3, 61, 1037, 8, 61, 1, 62, 1, 62, 1, 62, 5, 62, 1042, 8, 62, 10, 62, 12, 62, 1045, 9, 62, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 3, 63, 1056, 8, 63, 1, 63, 1, 63, 1, 63, 1, 63, 3, 63, 1062, 8, 63, 5, 63, 1064, 8, 63, 10, 63, 12, 63, 1067, 9, 63, 1, 64, 1, 64, 1, 64, 3, 64, 1072, 8, 64, 1, 64, 1, 64, 1, 65, 1, 65, 1, 65, 3, 65, 1079, 8, 65, 1, 65, 1, 65, 1, 66, 1, 66, 1, 66, 5, 66, 1086, 8, 66, 10, 66, 12, 66, 1089, 9, 66, 1, 67, 1, 67, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 3, 68, 1099, 8, 68, 3, 68, 1101, 8, 68, 1, 69, 3, 69, 1104, 8, 69, 1, 69, 1, 69, 1, 69, 1, 69, 1, 69, 1, 69, 3, 69, 1112, 8, 69, 1, 70, 1, 70, 1, 70, 3, 70, 1117, 8, 70, 1, 71, 1, 71, 1, 72, 1, 72, 1, 73, 1, 73, 1, 74, 1, 74, 3, 74, 1127, 8, 74, 1, 75, 1, 75, 1, 75, 3, 75, 1132, 8, 75, 1, 76, 1, 76, 1, 76, 1, 76, 1, 77, 1, 77, 1, 77, 1, 77, 1, 78, 1, 78, 3, 78, 1144, 8, 78, 1, 79, 1, 79, 5, 79, 1148, 8, 79, 10, 79, 12, 79, 1151, 9, 79, 1, 79, 1, 79, 1, 80, 1, 80, 1, 80, 1, 80, 1, 80, 3, 80, 1160, 8, 80, 1, 81, 1, 81, 5, 81, 1164, 8, 81, 10, 81, 12, 81, 1167, 9, 81, 1, 81, 1, 81, 1, 82, 1, 82, 1, 82, 1, 82, 1, 82, 3, 82, 1176, 8, 82, 1, 82, 0, 3, 68, 106, 126, 83, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 0, 16, 2, 0, 17, 17, 72, 72, 2, 0, 42, 42, 49, 49, 3, 0, 1, 1, 4, 4, 8, 8, 4, 0, 1, 1, 3, 4, 8, 8, 78, 78, 2, 0, 49, 49, 71, 71, 2, 0, 1, 1, 4, 4, 2, 0, 7, 7, 21, 22, 2, 0, 28, 28, 47, 47, 2, 0, 69, 69, 74, 74, 3, 0, 10, 10, 48, 48, 87, 87, 2, 0, 39, 39, 51, 51, 1, 0, 103, 104, 2, 0, 114, 114, 134, 134, 7, 0, 20, 20, 36, 36, 53, 54, 68, 68, 76, 76, 93, 93, 99, 99, 12, 0, 1, 19, 21, 28, 30, 35, 37, 40, 42, 49, 51, 52, 56, 56, 58, 67, 69, 75, 77, 92, 94, 95, 97, 98, 4, 0, 19, 19, 28, 28, 37, 37, 46, 46, 1314, 0, 169, 1, 0, 0, 0, 2, 176, 1, 0, 0, 0, 4, 178, 1, 0, 0, 0, 6, 180, 1, 0, 0, 0, 8, 189, 1, 0, 0, 0, 10, 195, 1, 0, 0, 0, 12, 212, 1, 0, 0, 0, 14, 214, 1, 0, 0, 0, 16, 217, 1, 0, 0, 0, 18, 226, 1, 0, 0, 0, 20, 232, 1, 0, 0, 0, 22, 236, 1, 0, 0, 0, 24, 245, 1, 0, 0, 0, 26, 247, 1, 0, 0, 0, 28, 256, 1, 0, 0, 0, 30, 260, 1, 0, 0, 0, 32, 271, 1, 0, 0, 0, 34, 275, 1, 0, 0, 0, 36, 290, 1, 0, 0, 0, 38, 293, 1, 0, 0, 0, 40, 342, 1, 0, 0, 0, 42, 345, 1, 0, 0, 0, 44, 351, 1, 0, 0, 0, 46, 355, 1, 0, 0, 0, 48, 361, 1, 0, 0, 0, 50, 379, 1, 0, 0, 0, 52, 382, 1, 0, 0, 0, 54, 385, 1, 0, 0, 0, 56, 395, 1, 0, 0, 0, 58, 398, 1, 0, 0, 0, 60, 402, 1, 0, 0, 0, 62, 435, 1, 0, 0, 0, 64, 437, 1, 0, 0, 0, 66, 440, 1, 0, 0, 0, 68, 455, 1, 0, 0, 0, 70, 517, 1, 0, 0, 0, 72, 522, 1, 0, 0, 0, 74, 533, 1, 0, 0, 0, 76, 535, 1, 0, 0, 0, 78, 541, 1, 0, 0, 0, 80, 549, 1, 0, 0, 0, 82, 567, 1, 0, 0, 0, 84, 569, 1, 0, 0, 0, 86, 577, 1, 0, 0, 0, 88, 582, 1, 0, 0, 0, 90, 590, 1, 0, 0, 0, 92, 594, 1, 0, 0, 0, 94, 598, 1, 0, 0, 0, 96, 607, 1, 0, 0, 0, 98, 621, 1, 0, 0, 0, 100, 623, 1, 0, 0, 0, 102, 673, 1, 0, 0, 0, 104, 675, 1, 0, 0, 0, 106, 832, 1, 0, 0, 0, 108, 930, 1, 0, 0, 0, 110, 940, 1, 0, 0, 0, 112, 961, 1, 0, 0, 0, 114, 994, 1, 0, 0, 0, 116, 1007, 1, 0, 0, 0, 118, 1009, 1, 0, 0, 0, 120, 1027, 1, 0, 0, 0, 122, 1036, 1, 0, 0, 0, 124, 1038, 1, 0, 0, 0, 126, 1055, 1, 0, 0, 0, 128, 1068, 1, 0, 0, 0, 130, 1078, 1, 0, 0, 0, 132, 1082, 1, 0, 0, 0, 134, 1090, 1, 0, 0, 0, 136, 1100, 1, 0, 0, 0, 138, 1103, 1, 0, 0, 0, 140, 1116, 1, 0, 0, 0, 142, 1118, 1, 0, 0, 0, 144, 1120, 1, 0, 0, 0, 146, 1122, 1, 0, 0, 0, 148, 1126, 1, 0, 0, 0, 150, 1131, 1, 0, 0, 0, 152, 1133, 1, 0, 0, 0, 154, 1137, 1, 0, 0, 0, 156, 1143, 1, 0, 0, 0, 158, 1145, 1, 0, 0, 0, 160, 1159, 1, 0, 0, 0, 162, 1161, 1, 0, 0, 0, 164, 1175, 1, 0, 0, 0, 166, 168, 3, 2, 1, 0, 167, 166, 1, 0, 0, 0, 168, 171, 1, 0, 0, 0, 169, 167, 1, 0, 0, 0, 169, 170, 1, 0, 0, 0, 170, 172, 1, 0, 0, 0, 171, 169, 1, 0, 0, 0, 172, 173, 5, 0, 0, 1, 173, 1, 1, 0, 0, 0, 174, 177, 3, 6, 3, 0, 175, 177, 3, 12, 6, 0, 176, 174, 1, 0, 0, 0, 176, 175, 1, 0, 0, 0, 177, 3, 1, 0, 0, 0, 178, 179, 3, 106, 53, 0, 179, 5, 1, 0, 0, 0, 180, 181, 5, 50, 0, 0, 181, 185, 3, 150, 75, 0, 182, 183, 5, 111, 0, 0, 183, 184, 5, 118, 0, 0, 184, 186, 3, 4, 2, 0, 185, 182, 1, 0, 0, 0, 185, 186, 1, 0, 0, 0, 186, 187, 1, 0, 0, 0, 187, 188, 5, 145, 0, 0, 188, 7, 1, 0, 0, 0, 189, 190, 3, 4, 2, 0, 190, 191, 5, 111, 0, 0, 191, 192, 5, 118, 0, 0, 192, 193, 3, 4, 2, 0, 193, 194, 5, 145, 0, 0, 194, 9, 1, 0, 0, 0, 195, 200, 3, 150, 75, 0, 196, 197, 5, 112, 0, 0, 197, 199, 3, 150, 75, 0, 198, 196, 1, 0, 0, 0, 199, 202, 1, 0, 0, 0, 200, 198, 1, 0, 0, 0, 200, 201, 1, 0, 0, 0, 201, 11, 1, 0, 0, 0, 202, 200, 1, 0, 0, 0, 203, 213, 3, 20, 10, 0, 204, 213, 3, 24, 12, 0, 205, 213, 3, 14, 7, 0, 206, 213, 3, 16, 8, 0, 207, 213, 3, 18, 9, 0, 208, 213, 3, 22, 11, 0, 209, 213, 3, 8, 4, 0, 210, 213, 3, 20, 10, 0, 211, 213, 3, 26, 13, 0, 212, 203, 1, 0, 0, 0, 212, 204, 1, 0, 0, 0, 212, 205, 1, 0, 0, 0, 212, 206, 1, 0, 0, 0, 212, 207, 1, 0, 0, 0, 212, 208, 1, 0, 0, 0, 212, 209, 1, 0, 0, 0, 212, 210, 1, 0, 0, 0, 212, 211, 1, 0, 0, 0, 213, 13, 1, 0, 0, 0, 214, 215, 3, 4, 2, 0, 215, 216, 5, 145, 0, 0, 216, 15, 1, 0, 0, 0, 217, 218, 5, 38, 0, 0, 218, 219, 5, 126, 0, 0, 219, 220, 3, 4, 2, 0, 220, 221, 5, 144, 0, 0, 221, 224, 3, 12, 6, 0, 222, 223, 5, 24, 0, 0, 223, 225, 3, 12, 6, 0, 224, 222, 1, 0, 0, 0, 224, 225, 1, 0, 0, 0, 225, 17, 1, 0, 0, 0, 226, 227, 5, 96, 0, 0, 227, 228, 5, 126, 0, 0, 228, 229, 3, 4, 2, 0, 229, 230, 5, 144, 0, 0, 230, 231, 3, 12, 6, 0, 231, 19, 1, 0, 0, 0, 232, 233, 5, 70, 0, 0, 233, 234, 3, 4, 2, 0, 234, 235, 5, 145, 0, 0, 235, 21, 1, 0, 0, 0, 236, 237, 5, 29, 0, 0, 237, 238, 3, 150, 75, 0, 238, 240, 5, 126, 0, 0, 239, 241, 3, 10, 5, 0, 240, 239, 1, 0, 0, 0, 240, 241, 1, 0, 0, 0, 241, 242, 1, 0, 0, 0, 242, 243, 5, 144, 0, 0, 243, 244, 3, 26, 13, 0, 244, 23, 1, 0, 0, 0, 245, 246, 5, 145, 0, 0, 246, 25, 1, 0, 0, 0, 247, 251, 5, 124, 0, 0, 248, 250, 3, 2, 1, 0, 249, 248, 1, 0, 0, 0, 250, 253, 1, 0, 0, 0, 251, 249, 1, 0, 0, 0, 251, 252, 1, 0, 0, 0, 252, 254, 1, 0, 0, 0, 253, 251, 1, 0, 0, 0, 254, 255, 5, 142, 0, 0, 255, 27, 1, 0, 0, 0, 256, 257, 3, 4, 2, 0, 257, 258, 5, 111, 0, 0, 258, 259, 3, 4, 2, 0, 259, 29, 1, 0, 0, 0, 260, 265, 3, 28, 14, 0, 261, 262, 5, 112, 0, 0, 262, 264, 3, 28, 14, 0, 263, 261, 1, 0, 0, 0, 264, 267, 1, 0, 0, 0, 265, 263, 1, 0, 0, 0, 265, 266, 1, 0, 0, 0, 266, 31, 1, 0, 0, 0, 267, 265, 1, 0, 0, 0, 268, 272, 3, 34, 17, 0, 269, 272, 3, 38, 19, 0, 270, 272, 3, 114, 57, 0, 271, 268, 1, 0, 0, 0, 271, 269, 1, 0, 0, 0, 271, 270, 1, 0, 0, 0, 272, 273, 1, 0, 0, 0, 273, 274, 5, 0, 0, 1, 274, 33, 1, 0, 0, 0, 275, 281, 3, 36, 18, 0, 276, 277, 5, 91, 0, 0, 277, 278, 5, 1, 0, 0, 278, 280, 3, 36, 18, 0, 279, 276, 1, 0, 0, 0, 280, 283, 1, 0, 0, 0, 281, 279, 1, 0, 0, 0, 281, 282, 1, 0, 0, 0, 282, 35, 1, 0, 0, 0, 283, 281, 1, 0, 0, 0, 284, 291, 3, 38, 19, 0, 285, 286, 5, 126, 0, 0, 286, 287, 3, 34, 17, 0, 287, 288, 5, 144, 0, 0, 288, 291, 1, 0, 0, 0, 289, 291, 3, 154, 77, 0, 290, 284, 1, 0, 0, 0, 290, 285, 1, 0, 0, 0, 290, 289, 1, 0, 0, 0, 291, 37, 1, 0, 0, 0, 292, 294, 3, 40, 20, 0, 293, 292, 1, 0, 0, 0, 293, 294, 1, 0, 0, 0, 294, 295, 1, 0, 0, 0, 295, 297, 5, 77, 0, 0, 296, 298, 5, 23, 0, 0, 297, 296, 1, 0, 0, 0, 297, 298, 1, 0, 0, 0, 298, 300, 1, 0, 0, 0, 299, 301, 3, 42, 21, 0, 300, 299, 1, 0, 0, 0, 300, 301, 1, 0, 0, 0, 301, 302, 1, 0, 0, 0, 302, 304, 3, 104, 52, 0, 303, 305, 3, 44, 22, 0, 304, 303, 1, 0, 0, 0, 304, 305, 1, 0, 0, 0, 305, 307, 1, 0, 0, 0, 306, 308, 3, 46, 23, 0, 307, 306, 1, 0, 0, 0, 307, 308, 1, 0, 0, 0, 308, 310, 1, 0, 0, 0, 309, 311, 3, 50, 25, 0, 310, 309, 1, 0, 0, 0, 310, 311, 1, 0, 0, 0, 311, 313, 1, 0, 0, 0, 312, 314, 3, 52, 26, 0, 313, 312, 1, 0, 0, 0, 313, 314, 1, 0, 0, 0, 314, 316, 1, 0, 0, 0, 315, 317, 3, 54, 27, 0, 316, 315, 1, 0, 0, 0, 316, 317, 1, 0, 0, 0, 317, 320, 1, 0, 0, 0, 318, 319, 5, 98, 0, 0, 319, 321, 7, 0, 0, 0, 320, 318, 1, 0, 0, 0, 320, 321, 1, 0, 0, 0, 321, 324, 1, 0, 0, 0, 322, 323, 5, 98, 0, 0, 323, 325, 5, 86, 0, 0, 324, 322, 1, 0, 0, 0, 324, 325, 1, 0, 0, 0, 325, 327, 1, 0, 0, 0, 326, 328, 3, 56, 28, 0, 327, 326, 1, 0, 0, 0, 327, 328, 1, 0, 0, 0, 328, 330, 1, 0, 0, 0, 329, 331, 3, 48, 24, 0, 330, 329, 1, 0, 0, 0, 330, 331, 1, 0, 0, 0, 331, 333, 1, 0, 0, 0, 332, 334, 3, 58, 29, 0, 333, 332, 1, 0, 0, 0, 333, 334, 1, 0, 0, 0, 334, 337, 1, 0, 0, 0, 335, 338, 3, 62, 31, 0, 336, 338, 3, 64, 32, 0, 337, 335, 1, 0, 0, 0, 337, 336, 1, 0, 0, 0, 337, 338, 1, 0, 0, 0, 338, 340, 1, 0, 0, 0, 339, 341, 3, 66, 33, 0, 340, 339, 1, 0, 0, 0, 340, 341, 1, 0, 0, 0, 341, 39, 1, 0, 0, 0, 342, 343, 5, 98, 0, 0, 343, 344, 3, 118, 59, 0, 344, 41, 1, 0, 0, 0, 345, 346, 5, 85, 0, 0, 346, 349, 5, 104, 0, 0, 347, 348, 5, 98, 0, 0, 348, 350, 5, 82, 0, 0, 349, 347, 1, 0, 0, 0, 349, 350, 1, 0, 0, 0, 350, 43, 1, 0, 0, 0, 351, 352, 5, 32, 0, 0, 352, 353, 3, 68, 34, 0, 353, 45, 1, 0, 0, 0, 354, 356, 7, 1, 0, 0, 355, 354, 1, 0, 0, 0, 355, 356, 1, 0, 0, 0, 356, 357, 1, 0, 0, 0, 357, 358, 5, 5, 0, 0, 358, 359, 5, 45, 0, 0, 359, 360, 3, 104, 52, 0, 360, 47, 1, 0, 0, 0, 361, 362, 5, 97, 0, 0, 362, 363, 3, 150, 75, 0, 363, 364, 5, 6, 0, 0, 364, 365, 5, 126, 0, 0, 365, 366, 3, 88, 44, 0, 366, 376, 5, 144, 0, 0, 367, 368, 5, 112, 0, 0, 368, 369, 3, 150, 75, 0, 369, 370, 5, 6, 0, 0, 370, 371, 5, 126, 0, 0, 371, 372, 3, 88, 44, 0, 372, 373, 5, 144, 0, 0, 373, 375, 1, 0, 0, 0, 374, 367, 1, 0, 0, 0, 375, 378, 1, 0, 0, 0, 376, 374, 1, 0, 0, 0, 376, 377, 1, 0, 0, 0, 377, 49, 1, 0, 0, 0, 378, 376, 1, 0, 0, 0, 379, 380, 5, 67, 0, 0, 380, 381, 3, 106, 53, 0, 381, 51, 1, 0, 0, 0, 382, 383, 5, 95, 0, 0, 383, 384, 3, 106, 53, 0, 384, 53, 1, 0, 0, 0, 385, 386, 5, 34, 0, 0, 386, 393, 5, 11, 0, 0, 387, 388, 7, 0, 0, 0, 388, 389, 5, 126, 0, 0, 389, 390, 3, 104, 52, 0, 390, 391, 5, 144, 0, 0, 391, 394, 1, 0, 0, 0, 392, 394, 3, 104, 52, 0, 393, 387, 1, 0, 0, 0, 393, 392, 1, 0, 0, 0, 394, 55, 1, 0, 0, 0, 395, 396, 5, 35, 0, 0, 396, 397, 3, 106, 53, 0, 397, 57, 1, 0, 0, 0, 398, 399, 5, 62, 0, 0, 399, 400, 5, 11, 0, 0, 400, 401, 3, 78, 39, 0, 401, 59, 1, 0, 0, 0, 402, 403, 5, 62, 0, 0, 403, 404, 5, 11, 0, 0, 404, 405, 3, 104, 52, 0, 405, 61, 1, 0, 0, 0, 406, 407, 5, 52, 0, 0, 407, 410, 3, 106, 53, 0, 408, 409, 5, 112, 0, 0, 409, 411, 3, 106, 53, 0, 410, 408, 1, 0, 0, 0, 410, 411, 1, 0, 0, 0, 411, 416, 1, 0, 0, 0, 412, 413, 5, 98, 0, 0, 413, 417, 5, 82, 0, 0, 414, 415, 5, 11, 0, 0, 415, 417, 3, 104, 52, 0, 416, 412, 1, 0, 0, 0, 416, 414, 1, 0, 0, 0, 416, 417, 1, 0, 0, 0, 417, 436, 1, 0, 0, 0, 418, 419, 5, 52, 0, 0, 419, 422, 3, 106, 53, 0, 420, 421, 5, 98, 0, 0, 421, 423, 5, 82, 0, 0, 422, 420, 1, 0, 0, 0, 422, 423, 1, 0, 0, 0, 423, 424, 1, 0, 0, 0, 424, 425, 5, 59, 0, 0, 425, 426, 3, 106, 53, 0, 426, 436, 1, 0, 0, 0, 427, 428, 5, 52, 0, 0, 428, 429, 3, 106, 53, 0, 429, 430, 5, 59, 0, 0, 430, 433, 3, 106, 53, 0, 431, 432, 5, 11, 0, 0, 432, 434, 3, 104, 52, 0, 433, 431, 1, 0, 0, 0, 433, 434, 1, 0, 0, 0, 434, 436, 1, 0, 0, 0, 435, 406, 1, 0, 0, 0, 435, 418, 1, 0, 0, 0, 435, 427, 1, 0, 0, 0, 436, 63, 1, 0, 0, 0, 437, 438, 5, 59, 0, 0, 438, 439, 3, 106, 53, 0, 439, 65, 1, 0, 0, 0, 440, 441, 5, 79, 0, 0, 441, 442, 3, 84, 42, 0, 442, 67, 1, 0, 0, 0, 443, 444, 6, 34, -1, 0, 444, 446, 3, 126, 63, 0, 445, 447, 5, 27, 0, 0, 446, 445, 1, 0, 0, 0, 446, 447, 1, 0, 0, 0, 447, 449, 1, 0, 0, 0, 448, 450, 3, 76, 38, 0, 449, 448, 1, 0, 0, 0, 449, 450, 1, 0, 0, 0, 450, 456, 1, 0, 0, 0, 451, 452, 5, 126, 0, 0, 452, 453, 3, 68, 34, 0, 453, 454, 5, 144, 0, 0, 454, 456, 1, 0, 0, 0, 455, 443, 1, 0, 0, 0, 455, 451, 1, 0, 0, 0, 456, 471, 1, 0, 0, 0, 457, 458, 10, 3, 0, 0, 458, 459, 3, 72, 36, 0, 459, 460, 3, 68, 34, 4, 460, 470, 1, 0, 0, 0, 461, 463, 10, 4, 0, 0, 462, 464, 3, 70, 35, 0, 463, 462, 1, 0, 0, 0, 463, 464, 1, 0, 0, 0, 464, 465, 1, 0, 0, 0, 465, 466, 5, 45, 0, 0, 466, 467, 3, 68, 34, 0, 467, 468, 3, 74, 37, 0, 468, 470, 1, 0, 0, 0, 469, 457, 1, 0, 0, 0, 469, 461, 1, 0, 0, 0, 470, 473, 1, 0, 0, 0, 471, 469, 1, 0, 0, 0, 471, 472, 1, 0, 0, 0, 472, 69, 1, 0, 0, 0, 473, 471, 1, 0, 0, 0, 474, 476, 7, 2, 0, 0, 475, 474, 1, 0, 0, 0, 475, 476, 1, 0, 0, 0, 476, 477, 1, 0, 0, 0, 477, 484, 5, 42, 0, 0, 478, 480, 5, 42, 0, 0, 479, 481, 7, 2, 0, 0, 480, 479, 1, 0, 0, 0, 480, 481, 1, 0, 0, 0, 481, 484, 1, 0, 0, 0, 482, 484, 7, 2, 0, 0, 483, 475, 1, 0, 0, 0, 483, 478, 1, 0, 0, 0, 483, 482, 1, 0, 0, 0, 484, 518, 1, 0, 0, 0, 485, 487, 7, 3, 0, 0, 486, 485, 1, 0, 0, 0, 486, 487, 1, 0, 0, 0, 487, 488, 1, 0, 0, 0, 488, 490, 7, 4, 0, 0, 489, 491, 5, 63, 0, 0, 490, 489, 1, 0, 0, 0, 490, 491, 1, 0, 0, 0, 491, 500, 1, 0, 0, 0, 492, 494, 7, 4, 0, 0, 493, 495, 5, 63, 0, 0, 494, 493, 1, 0, 0, 0, 494, 495, 1, 0, 0, 0, 495, 497, 1, 0, 0, 0, 496, 498, 7, 3, 0, 0, 497, 496, 1, 0, 0, 0, 497, 498, 1, 0, 0, 0, 498, 500, 1, 0, 0, 0, 499, 486, 1, 0, 0, 0, 499, 492, 1, 0, 0, 0, 500, 518, 1, 0, 0, 0, 501, 503, 7, 5, 0, 0, 502, 501, 1, 0, 0, 0, 502, 503, 1, 0, 0, 0, 503, 504, 1, 0, 0, 0, 504, 506, 5, 33, 0, 0, 505, 507, 5, 63, 0, 0, 506, 505, 1, 0, 0, 0, 506, 507, 1, 0, 0, 0, 507, 516, 1, 0, 0, 0, 508, 510, 5, 33, 0, 0, 509, 511, 5, 63, 0, 0, 510, 509, 1, 0, 0, 0, 510, 511, 1, 0, 0, 0, 511, 513, 1, 0, 0, 0, 512, 514, 7, 5, 0, 0, 513, 512, 1, 0, 0, 0, 513, 514, 1, 0, 0, 0, 514, 516, 1, 0, 0, 0, 515, 502, 1, 0, 0, 0, 515, 508, 1, 0, 0, 0, 516, 518, 1, 0, 0, 0, 517, 483, 1, 0, 0, 0, 517, 499, 1, 0, 0, 0, 517, 515, 1, 0, 0, 0, 518, 71, 1, 0, 0, 0, 519, 520, 5, 16, 0, 0, 520, 523, 5, 45, 0, 0, 521, 523, 5, 112, 0, 0, 522, 519, 1, 0, 0, 0, 522, 521, 1, 0, 0, 0, 523, 73, 1, 0, 0, 0, 524, 525, 5, 60, 0, 0, 525, 534, 3, 104, 52, 0, 526, 527, 5, 92, 0, 0, 527, 528, 5, 126, 0, 0, 528, 529, 3, 104, 52, 0, 529, 530, 5, 144, 0, 0, 530, 534, 1, 0, 0, 0, 531, 532, 5, 92, 0, 0, 532, 534, 3, 104, 52, 0, 533, 524, 1, 0, 0, 0, 533, 526, 1, 0, 0, 0, 533, 531, 1, 0, 0, 0, 534, 75, 1, 0, 0, 0, 535, 536, 5, 75, 0, 0, 536, 539, 3, 82, 41, 0, 537, 538, 5, 59, 0, 0, 538, 540, 3, 82, 41, 0, 539, 537, 1, 0, 0, 0, 539, 540, 1, 0, 0, 0, 540, 77, 1, 0, 0, 0, 541, 546, 3, 80, 40, 0, 542, 543, 5, 112, 0, 0, 543, 545, 3, 80, 40, 0, 544, 542, 1, 0, 0, 0, 545, 548, 1, 0, 0, 0, 546, 544, 1, 0, 0, 0, 546, 547, 1, 0, 0, 0, 547, 79, 1, 0, 0, 0, 548, 546, 1, 0, 0, 0, 549, 551, 3, 106, 53, 0, 550, 552, 7, 6, 0, 0, 551, 550, 1, 0, 0, 0, 551, 552, 1, 0, 0, 0, 552, 555, 1, 0, 0, 0, 553, 554, 5, 58, 0, 0, 554, 556, 7, 7, 0, 0, 555, 553, 1, 0, 0, 0, 555, 556, 1, 0, 0, 0, 556, 559, 1, 0, 0, 0, 557, 558, 5, 15, 0, 0, 558, 560, 5, 106, 0, 0, 559, 557, 1, 0, 0, 0, 559, 560, 1, 0, 0, 0, 560, 81, 1, 0, 0, 0, 561, 568, 3, 154, 77, 0, 562, 565, 3, 138, 69, 0, 563, 564, 5, 146, 0, 0, 564, 566, 3, 138, 69, 0, 565, 563, 1, 0, 0, 0, 565, 566, 1, 0, 0, 0, 566, 568, 1, 0, 0, 0, 567, 561, 1, 0, 0, 0, 567, 562, 1, 0, 0, 0, 568, 83, 1, 0, 0, 0, 569, 574, 3, 86, 43, 0, 570, 571, 5, 112, 0, 0, 571, 573, 3, 86, 43, 0, 572, 570, 1, 0, 0, 0, 573, 576, 1, 0, 0, 0, 574, 572, 1, 0, 0, 0, 574, 575, 1, 0, 0, 0, 575, 85, 1, 0, 0, 0, 576, 574, 1, 0, 0, 0, 577, 578, 3, 150, 75, 0, 578, 579, 5, 118, 0, 0, 579, 580, 3, 140, 70, 0, 580, 87, 1, 0, 0, 0, 581, 583, 3, 90, 45, 0, 582, 581, 1, 0, 0, 0, 582, 583, 1, 0, 0, 0, 583, 585, 1, 0, 0, 0, 584, 586, 3, 92, 46, 0, 585, 584, 1, 0, 0, 0, 585, 586, 1, 0, 0, 0, 586, 588, 1, 0, 0, 0, 587, 589, 3, 94, 47, 0, 588, 587, 1, 0, 0, 0, 588, 589, 1, 0, 0, 0, 589, 89, 1, 0, 0, 0, 590, 591, 5, 65, 0, 0, 591, 592, 5, 11, 0, 0, 592, 593, 3, 104, 52, 0, 593, 91, 1, 0, 0, 0, 594, 595, 5, 62, 0, 0, 595, 596, 5, 11, 0, 0, 596, 597, 3, 78, 39, 0, 597, 93, 1, 0, 0, 0, 598, 599, 7, 8, 0, 0, 599, 600, 3, 96, 48, 0, 600, 95, 1, 0, 0, 0, 601, 608, 3, 98, 49, 0, 602, 603, 5, 9, 0, 0, 603, 604, 3, 98, 49, 0, 604, 605, 5, 2, 0, 0, 605, 606, 3, 98, 49, 0, 606, 608, 1, 0, 0, 0, 607, 601, 1, 0, 0, 0, 607, 602, 1, 0, 0, 0, 608, 97, 1, 0, 0, 0, 609, 610, 5, 18, 0, 0, 610, 622, 5, 73, 0, 0, 611, 612, 5, 90, 0, 0, 612, 622, 5, 66, 0, 0, 613, 614, 5, 90, 0, 0, 614, 622, 5, 30, 0, 0, 615, 616, 3, 138, 69, 0, 616, 617, 5, 66, 0, 0, 617, 622, 1, 0, 0, 0, 618, 619, 3, 138, 69, 0, 619, 620, 5, 30, 0, 0, 620, 622, 1, 0, 0, 0, 621, 609, 1, 0, 0, 0, 621, 611, 1, 0, 0, 0, 621, 613, 1, 0, 0, 0, 621, 615, 1, 0, 0, 0, 621, 618, 1, 0, 0, 0, 622, 99, 1, 0, 0, 0, 623, 624, 3, 106, 53, 0, 624, 625, 5, 0, 0, 1, 625, 101, 1, 0, 0, 0, 626, 674, 3, 150, 75, 0, 627, 628, 3, 150, 75, 0, 628, 629, 5, 126, 0, 0, 629, 630, 3, 150, 75, 0, 630, 637, 3, 102, 51, 0, 631, 632, 5, 112, 0, 0, 632, 633, 3, 150, 75, 0, 633, 634, 3, 102, 51, 0, 634, 636, 1, 0, 0, 0, 635, 631, 1, 0, 0, 0, 636, 639, 1, 0, 0, 0, 637, 635, 1, 0, 0, 0, 637, 638, 1, 0, 0, 0, 638, 640, 1, 0, 0, 0, 639, 637, 1, 0, 0, 0, 640, 641, 5, 144, 0, 0, 641, 674, 1, 0, 0, 0, 642, 643, 3, 150, 75, 0, 643, 644, 5, 126, 0, 0, 644, 649, 3, 152, 76, 0, 645, 646, 5, 112, 0, 0, 646, 648, 3, 152, 76, 0, 647, 645, 1, 0, 0, 0, 648, 651, 1, 0, 0, 0, 649, 647, 1, 0, 0, 0, 649, 650, 1, 0, 0, 0, 650, 652, 1, 0, 0, 0, 651, 649, 1, 0, 0, 0, 652, 653, 5, 144, 0, 0, 653, 674, 1, 0, 0, 0, 654, 655, 3, 150, 75, 0, 655, 656, 5, 126, 0, 0, 656, 661, 3, 102, 51, 0, 657, 658, 5, 112, 0, 0, 658, 660, 3, 102, 51, 0, 659, 657, 1, 0, 0, 0, 660, 663, 1, 0, 0, 0, 661, 659, 1, 0, 0, 0, 661, 662, 1, 0, 0, 0, 662, 664, 1, 0, 0, 0, 663, 661, 1, 0, 0, 0, 664, 665, 5, 144, 0, 0, 665, 674, 1, 0, 0, 0, 666, 667, 3, 150, 75, 0, 667, 669, 5, 126, 0, 0, 668, 670, 3, 104, 52, 0, 669, 668, 1, 0, 0, 0, 669, 670, 1, 0, 0, 0, 670, 671, 1, 0, 0, 0, 671, 672, 5, 144, 0, 0, 672, 674, 1, 0, 0, 0, 673, 626, 1, 0, 0, 0, 673, 627, 1, 0, 0, 0, 673, 642, 1, 0, 0, 0, 673, 654, 1, 0, 0, 0, 673, 666, 1, 0, 0, 0, 674, 103, 1, 0, 0, 0, 675, 680, 3, 106, 53, 0, 676, 677, 5, 112, 0, 0, 677, 679, 3, 106, 53, 0, 678, 676, 1, 0, 0, 0, 679, 682, 1, 0, 0, 0, 680, 678, 1, 0, 0, 0, 680, 681, 1, 0, 0, 0, 681, 105, 1, 0, 0, 0, 682, 680, 1, 0, 0, 0, 683, 684, 6, 53, -1, 0, 684, 686, 5, 12, 0, 0, 685, 687, 3, 106, 53, 0, 686, 685, 1, 0, 0, 0, 686, 687, 1, 0, 0, 0, 687, 693, 1, 0, 0, 0, 688, 689, 5, 94, 0, 0, 689, 690, 3, 106, 53, 0, 690, 691, 5, 81, 0, 0, 691, 692, 3, 106, 53, 0, 692, 694, 1, 0, 0, 0, 693, 688, 1, 0, 0, 0, 694, 695, 1, 0, 0, 0, 695, 693, 1, 0, 0, 0, 695, 696, 1, 0, 0, 0, 696, 699, 1, 0, 0, 0, 697, 698, 5, 24, 0, 0, 698, 700, 3, 106, 53, 0, 699, 697, 1, 0, 0, 0, 699, 700, 1, 0, 0, 0, 700, 701, 1, 0, 0, 0, 701, 702, 5, 25, 0, 0, 702, 833, 1, 0, 0, 0, 703, 704, 5, 13, 0, 0, 704, 705, 5, 126, 0, 0, 705, 706, 3, 106, 53, 0, 706, 707, 5, 6, 0, 0, 707, 708, 3, 102, 51, 0, 708, 709, 5, 144, 0, 0, 709, 833, 1, 0, 0, 0, 710, 711, 5, 19, 0, 0, 711, 833, 5, 106, 0, 0, 712, 713, 5, 43, 0, 0, 713, 714, 3, 106, 53, 0, 714, 715, 3, 142, 71, 0, 715, 833, 1, 0, 0, 0, 716, 717, 5, 80, 0, 0, 717, 718, 5, 126, 0, 0, 718, 719, 3, 106, 53, 0, 719, 720, 5, 32, 0, 0, 720, 723, 3, 106, 53, 0, 721, 722, 5, 31, 0, 0, 722, 724, 3, 106, 53, 0, 723, 721, 1, 0, 0, 0, 723, 724, 1, 0, 0, 0, 724, 725, 1, 0, 0, 0, 725, 726, 5, 144, 0, 0, 726, 833, 1, 0, 0, 0, 727, 728, 5, 83, 0, 0, 728, 833, 5, 106, 0, 0, 729, 730, 5, 88, 0, 0, 730, 731, 5, 126, 0, 0, 731, 732, 7, 9, 0, 0, 732, 733, 3, 156, 78, 0, 733, 734, 5, 32, 0, 0, 734, 735, 3, 106, 53, 0, 735, 736, 5, 144, 0, 0, 736, 833, 1, 0, 0, 0, 737, 738, 3, 150, 75, 0, 738, 740, 5, 126, 0, 0, 739, 741, 3, 104, 52, 0, 740, 739, 1, 0, 0, 0, 740, 741, 1, 0, 0, 0, 741, 742, 1, 0, 0, 0, 742, 743, 5, 144, 0, 0, 743, 752, 1, 0, 0, 0, 744, 746, 5, 126, 0, 0, 745, 747, 5, 23, 0, 0, 746, 745, 1, 0, 0, 0, 746, 747, 1, 0, 0, 0, 747, 749, 1, 0, 0, 0, 748, 750, 3, 108, 54, 0, 749, 748, 1, 0, 0, 0, 749, 750, 1, 0, 0, 0, 750, 751, 1, 0, 0, 0, 751, 753, 5, 144, 0, 0, 752, 744, 1, 0, 0, 0, 752, 753, 1, 0, 0, 0, 753, 754, 1, 0, 0, 0, 754, 755, 5, 64, 0, 0, 755, 756, 5, 126, 0, 0, 756, 757, 3, 88, 44, 0, 757, 758, 5, 144, 0, 0, 758, 833, 1, 0, 0, 0, 759, 760, 3, 150, 75, 0, 760, 762, 5, 126, 0, 0, 761, 763, 3, 104, 52, 0, 762, 761, 1, 0, 0, 0, 762, 763, 1, 0, 0, 0, 763, 764, 1, 0, 0, 0, 764, 765, 5, 144, 0, 0, 765, 774, 1, 0, 0, 0, 766, 768, 5, 126, 0, 0, 767, 769, 5, 23, 0, 0, 768, 767, 1, 0, 0, 0, 768, 769, 1, 0, 0, 0, 769, 771, 1, 0, 0, 0, 770, 772, 3, 108, 54, 0, 771, 770, 1, 0, 0, 0, 771, 772, 1, 0, 0, 0, 772, 773, 1, 0, 0, 0, 773, 775, 5, 144, 0, 0, 774, 766, 1, 0, 0, 0, 774, 775, 1, 0, 0, 0, 775, 776, 1, 0, 0, 0, 776, 777, 5, 64, 0, 0, 777, 778, 3, 150, 75, 0, 778, 833, 1, 0, 0, 0, 779, 785, 3, 150, 75, 0, 780, 782, 5, 126, 0, 0, 781, 783, 3, 104, 52, 0, 782, 781, 1, 0, 0, 0, 782, 783, 1, 0, 0, 0, 783, 784, 1, 0, 0, 0, 784, 786, 5, 144, 0, 0, 785, 780, 1, 0, 0, 0, 785, 786, 1, 0, 0, 0, 786, 787, 1, 0, 0, 0, 787, 789, 5, 126, 0, 0, 788, 790, 5, 23, 0, 0, 789, 788, 1, 0, 0, 0, 789, 790, 1, 0, 0, 0, 790, 792, 1, 0, 0, 0, 791, 793, 3, 108, 54, 0, 792, 791, 1, 0, 0, 0, 792, 793, 1, 0, 0, 0, 793, 794, 1, 0, 0, 0, 794, 795, 5, 144, 0, 0, 795, 833, 1, 0, 0, 0, 796, 833, 3, 114, 57, 0, 797, 833, 3, 158, 79, 0, 798, 833, 3, 140, 70, 0, 799, 800, 5, 114, 0, 0, 800, 833, 3, 106, 53, 19, 801, 802, 5, 56, 0, 0, 802, 833, 3, 106, 53, 13, 803, 804, 3, 130, 65, 0, 804, 805, 5, 116, 0, 0, 805, 807, 1, 0, 0, 0, 806, 803, 1, 0, 0, 0, 806, 807, 1, 0, 0, 0, 807, 808, 1, 0, 0, 0, 808, 833, 5, 108, 0, 0, 809, 810, 5, 126, 0, 0, 810, 811, 3, 34, 17, 0, 811, 812, 5, 144, 0, 0, 812, 833, 1, 0, 0, 0, 813, 814, 5, 126, 0, 0, 814, 815, 3, 106, 53, 0, 815, 816, 5, 144, 0, 0, 816, 833, 1, 0, 0, 0, 817, 818, 5, 126, 0, 0, 818, 819, 3, 104, 52, 0, 819, 820, 5, 144, 0, 0, 820, 833, 1, 0, 0, 0, 821, 823, 5, 125, 0, 0, 822, 824, 3, 104, 52, 0, 823, 822, 1, 0, 0, 0, 823, 824, 1, 0, 0, 0, 824, 825, 1, 0, 0, 0, 825, 833, 5, 143, 0, 0, 826, 828, 5, 124, 0, 0, 827, 829, 3, 30, 15, 0, 828, 827, 1, 0, 0, 0, 828, 829, 1, 0, 0, 0, 829, 830, 1, 0, 0, 0, 830, 833, 5, 142, 0, 0, 831, 833, 3, 122, 61, 0, 832, 683, 1, 0, 0, 0, 832, 703, 1, 0, 0, 0, 832, 710, 1, 0, 0, 0, 832, 712, 1, 0, 0, 0, 832, 716, 1, 0, 0, 0, 832, 727, 1, 0, 0, 0, 832, 729, 1, 0, 0, 0, 832, 737, 1, 0, 0, 0, 832, 759, 1, 0, 0, 0, 832, 779, 1, 0, 0, 0, 832, 796, 1, 0, 0, 0, 832, 797, 1, 0, 0, 0, 832, 798, 1, 0, 0, 0, 832, 799, 1, 0, 0, 0, 832, 801, 1, 0, 0, 0, 832, 806, 1, 0, 0, 0, 832, 809, 1, 0, 0, 0, 832, 813, 1, 0, 0, 0, 832, 817, 1, 0, 0, 0, 832, 821, 1, 0, 0, 0, 832, 826, 1, 0, 0, 0, 832, 831, 1, 0, 0, 0, 833, 927, 1, 0, 0, 0, 834, 838, 10, 18, 0, 0, 835, 839, 5, 108, 0, 0, 836, 839, 5, 146, 0, 0, 837, 839, 5, 133, 0, 0, 838, 835, 1, 0, 0, 0, 838, 836, 1, 0, 0, 0, 838, 837, 1, 0, 0, 0, 839, 840, 1, 0, 0, 0, 840, 926, 3, 106, 53, 19, 841, 845, 10, 17, 0, 0, 842, 846, 5, 134, 0, 0, 843, 846, 5, 114, 0, 0, 844, 846, 5, 113, 0, 0, 845, 842, 1, 0, 0, 0, 845, 843, 1, 0, 0, 0, 845, 844, 1, 0, 0, 0, 846, 847, 1, 0, 0, 0, 847, 926, 3, 106, 53, 18, 848, 873, 10, 16, 0, 0, 849, 874, 5, 117, 0, 0, 850, 874, 5, 118, 0, 0, 851, 874, 5, 129, 0, 0, 852, 874, 5, 127, 0, 0, 853, 874, 5, 128, 0, 0, 854, 874, 5, 119, 0, 0, 855, 874, 5, 120, 0, 0, 856, 858, 5, 56, 0, 0, 857, 856, 1, 0, 0, 0, 857, 858, 1, 0, 0, 0, 858, 859, 1, 0, 0, 0, 859, 861, 5, 40, 0, 0, 860, 862, 5, 14, 0, 0, 861, 860, 1, 0, 0, 0, 861, 862, 1, 0, 0, 0, 862, 874, 1, 0, 0, 0, 863, 865, 5, 56, 0, 0, 864, 863, 1, 0, 0, 0, 864, 865, 1, 0, 0, 0, 865, 866, 1, 0, 0, 0, 866, 874, 7, 10, 0, 0, 867, 874, 5, 140, 0, 0, 868, 874, 5, 141, 0, 0, 869, 874, 5, 131, 0, 0, 870, 874, 5, 122, 0, 0, 871, 874, 5, 123, 0, 0, 872, 874, 5, 130, 0, 0, 873, 849, 1, 0, 0, 0, 873, 850, 1, 0, 0, 0, 873, 851, 1, 0, 0, 0, 873, 852, 1, 0, 0, 0, 873, 853, 1, 0, 0, 0, 873, 854, 1, 0, 0, 0, 873, 855, 1, 0, 0, 0, 873, 857, 1, 0, 0, 0, 873, 864, 1, 0, 0, 0, 873, 867, 1, 0, 0, 0, 873, 868, 1, 0, 0, 0, 873, 869, 1, 0, 0, 0, 873, 870, 1, 0, 0, 0, 873, 871, 1, 0, 0, 0, 873, 872, 1, 0, 0, 0, 874, 875, 1, 0, 0, 0, 875, 926, 3, 106, 53, 17, 876, 877, 10, 14, 0, 0, 877, 878, 5, 132, 0, 0, 878, 926, 3, 106, 53, 15, 879, 880, 10, 12, 0, 0, 880, 881, 5, 2, 0, 0, 881, 926, 3, 106, 53, 13, 882, 883, 10, 11, 0, 0, 883, 884, 5, 61, 0, 0, 884, 926, 3, 106, 53, 12, 885, 887, 10, 10, 0, 0, 886, 888, 5, 56, 0, 0, 887, 886, 1, 0, 0, 0, 887, 888, 1, 0, 0, 0, 888, 889, 1, 0, 0, 0, 889, 890, 5, 9, 0, 0, 890, 891, 3, 106, 53, 0, 891, 892, 5, 2, 0, 0, 892, 893, 3, 106, 53, 11, 893, 926, 1, 0, 0, 0, 894, 895, 10, 9, 0, 0, 895, 896, 5, 135, 0, 0, 896, 897, 3, 106, 53, 0, 897, 898, 5, 111, 0, 0, 898, 899, 3, 106, 53, 9, 899, 926, 1, 0, 0, 0, 900, 901, 10, 22, 0, 0, 901, 902, 5, 125, 0, 0, 902, 903, 3, 106, 53, 0, 903, 904, 5, 143, 0, 0, 904, 926, 1, 0, 0, 0, 905, 906, 10, 21, 0, 0, 906, 907, 5, 116, 0, 0, 907, 926, 5, 104, 0, 0, 908, 909, 10, 20, 0, 0, 909, 910, 5, 116, 0, 0, 910, 926, 3, 150, 75, 0, 911, 912, 10, 15, 0, 0, 912, 914, 5, 44, 0, 0, 913, 915, 5, 56, 0, 0, 914, 913, 1, 0, 0, 0, 914, 915, 1, 0, 0, 0, 915, 916, 1, 0, 0, 0, 916, 926, 5, 57, 0, 0, 917, 923, 10, 8, 0, 0, 918, 924, 3, 148, 74, 0, 919, 920, 5, 6, 0, 0, 920, 924, 3, 150, 75, 0, 921, 922, 5, 6, 0, 0, 922, 924, 5, 106, 0, 0, 923, 918, 1, 0, 0, 0, 923, 919, 1, 0, 0, 0, 923, 921, 1, 0, 0, 0, 924, 926, 1, 0, 0, 0, 925, 834, 1, 0, 0, 0, 925, 841, 1, 0, 0, 0, 925, 848, 1, 0, 0, 0, 925, 876, 1, 0, 0, 0, 925, 879, 1, 0, 0, 0, 925, 882, 1, 0, 0, 0, 925, 885, 1, 0, 0, 0, 925, 894, 1, 0, 0, 0, 925, 900, 1, 0, 0, 0, 925, 905, 1, 0, 0, 0, 925, 908, 1, 0, 0, 0, 925, 911, 1, 0, 0, 0, 925, 917, 1, 0, 0, 0, 926, 929, 1, 0, 0, 0, 927, 925, 1, 0, 0, 0, 927, 928, 1, 0, 0, 0, 928, 107, 1, 0, 0, 0, 929, 927, 1, 0, 0, 0, 930, 935, 3, 110, 55, 0, 931, 932, 5, 112, 0, 0, 932, 934, 3, 110, 55, 0, 933, 931, 1, 0, 0, 0, 934, 937, 1, 0, 0, 0, 935, 933, 1, 0, 0, 0, 935, 936, 1, 0, 0, 0, 936, 109, 1, 0, 0, 0, 937, 935, 1, 0, 0, 0, 938, 941, 3, 112, 56, 0, 939, 941, 3, 106, 53, 0, 940, 938, 1, 0, 0, 0, 940, 939, 1, 0, 0, 0, 941, 111, 1, 0, 0, 0, 942, 943, 5, 126, 0, 0, 943, 948, 3, 150, 75, 0, 944, 945, 5, 112, 0, 0, 945, 947, 3, 150, 75, 0, 946, 944, 1, 0, 0, 0, 947, 950, 1, 0, 0, 0, 948, 946, 1, 0, 0, 0, 948, 949, 1, 0, 0, 0, 949, 951, 1, 0, 0, 0, 950, 948, 1, 0, 0, 0, 951, 952, 5, 144, 0, 0, 952, 962, 1, 0, 0, 0, 953, 958, 3, 150, 75, 0, 954, 955, 5, 112, 0, 0, 955, 957, 3, 150, 75, 0, 956, 954, 1, 0, 0, 0, 957, 960, 1, 0, 0, 0, 958, 956, 1, 0, 0, 0, 958, 959, 1, 0, 0, 0, 959, 962, 1, 0, 0, 0, 960, 958, 1, 0, 0, 0, 961, 942, 1, 0, 0, 0, 961, 953, 1, 0, 0, 0, 962, 963, 1, 0, 0, 0, 963, 964, 5, 107, 0, 0, 964, 965, 3, 106, 53, 0, 965, 113, 1, 0, 0, 0, 966, 967, 5, 128, 0, 0, 967, 971, 3, 150, 75, 0, 968, 970, 3, 116, 58, 0, 969, 968, 1, 0, 0, 0, 970, 973, 1, 0, 0, 0, 971, 969, 1, 0, 0, 0, 971, 972, 1, 0, 0, 0, 972, 974, 1, 0, 0, 0, 973, 971, 1, 0, 0, 0, 974, 975, 5, 146, 0, 0, 975, 976, 5, 120, 0, 0, 976, 995, 1, 0, 0, 0, 977, 978, 5, 128, 0, 0, 978, 982, 3, 150, 75, 0, 979, 981, 3, 116, 58, 0, 980, 979, 1, 0, 0, 0, 981, 984, 1, 0, 0, 0, 982, 980, 1, 0, 0, 0, 982, 983, 1, 0, 0, 0, 983, 985, 1, 0, 0, 0, 984, 982, 1, 0, 0, 0, 985, 987, 5, 120, 0, 0, 986, 988, 3, 114, 57, 0, 987, 986, 1, 0, 0, 0, 987, 988, 1, 0, 0, 0, 988, 989, 1, 0, 0, 0, 989, 990, 5, 128, 0, 0, 990, 991, 5, 146, 0, 0, 991, 992, 3, 150, 75, 0, 992, 993, 5, 120, 0, 0, 993, 995, 1, 0, 0, 0, 994, 966, 1, 0, 0, 0, 994, 977, 1, 0, 0, 0, 995, 115, 1, 0, 0, 0, 996, 997, 3, 150, 75, 0, 997, 998, 5, 118, 0, 0, 998, 999, 3, 156, 78, 0, 999, 1008, 1, 0, 0, 0, 1000, 1001, 3, 150, 75, 0, 1001, 1002, 5, 118, 0, 0, 1002, 1003, 5, 124, 0, 0, 1003, 1004, 3, 106, 53, 0, 1004, 1005, 5, 142, 0, 0, 1005, 1008, 1, 0, 0, 0, 1006, 1008, 3, 150, 75, 0, 1007, 996, 1, 0, 0, 0, 1007, 1000, 1, 0, 0, 0, 1007, 1006, 1, 0, 0, 0, 1008, 117, 1, 0, 0, 0, 1009, 1014, 3, 120, 60, 0, 1010, 1011, 5, 112, 0, 0, 1011, 1013, 3, 120, 60, 0, 1012, 1010, 1, 0, 0, 0, 1013, 1016, 1, 0, 0, 0, 1014, 1012, 1, 0, 0, 0, 1014, 1015, 1, 0, 0, 0, 1015, 119, 1, 0, 0, 0, 1016, 1014, 1, 0, 0, 0, 1017, 1018, 3, 150, 75, 0, 1018, 1019, 5, 6, 0, 0, 1019, 1020, 5, 126, 0, 0, 1020, 1021, 3, 34, 17, 0, 1021, 1022, 5, 144, 0, 0, 1022, 1028, 1, 0, 0, 0, 1023, 1024, 3, 106, 53, 0, 1024, 1025, 5, 6, 0, 0, 1025, 1026, 3, 150, 75, 0, 1026, 1028, 1, 0, 0, 0, 1027, 1017, 1, 0, 0, 0, 1027, 1023, 1, 0, 0, 0, 1028, 121, 1, 0, 0, 0, 1029, 1037, 3, 154, 77, 0, 1030, 1031, 3, 130, 65, 0, 1031, 1032, 5, 116, 0, 0, 1032, 1034, 1, 0, 0, 0, 1033, 1030, 1, 0, 0, 0, 1033, 1034, 1, 0, 0, 0, 1034, 1035, 1, 0, 0, 0, 1035, 1037, 3, 124, 62, 0, 1036, 1029, 1, 0, 0, 0, 1036, 1033, 1, 0, 0, 0, 1037, 123, 1, 0, 0, 0, 1038, 1043, 3, 150, 75, 0, 1039, 1040, 5, 116, 0, 0, 1040, 1042, 3, 150, 75, 0, 1041, 1039, 1, 0, 0, 0, 1042, 1045, 1, 0, 0, 0, 1043, 1041, 1, 0, 0, 0, 1043, 1044, 1, 0, 0, 0, 1044, 125, 1, 0, 0, 0, 1045, 1043, 1, 0, 0, 0, 1046, 1047, 6, 63, -1, 0, 1047, 1056, 3, 130, 65, 0, 1048, 1056, 3, 128, 64, 0, 1049, 1050, 5, 126, 0, 0, 1050, 1051, 3, 34, 17, 0, 1051, 1052, 5, 144, 0, 0, 1052, 1056, 1, 0, 0, 0, 1053, 1056, 3, 114, 57, 0, 1054, 1056, 3, 154, 77, 0, 1055, 1046, 1, 0, 0, 0, 1055, 1048, 1, 0, 0, 0, 1055, 1049, 1, 0, 0, 0, 1055, 1053, 1, 0, 0, 0, 1055, 1054, 1, 0, 0, 0, 1056, 1065, 1, 0, 0, 0, 1057, 1061, 10, 3, 0, 0, 1058, 1062, 3, 148, 74, 0, 1059, 1060, 5, 6, 0, 0, 1060, 1062, 3, 150, 75, 0, 1061, 1058, 1, 0, 0, 0, 1061, 1059, 1, 0, 0, 0, 1062, 1064, 1, 0, 0, 0, 1063, 1057, 1, 0, 0, 0, 1064, 1067, 1, 0, 0, 0, 1065, 1063, 1, 0, 0, 0, 1065, 1066, 1, 0, 0, 0, 1066, 127, 1, 0, 0, 0, 1067, 1065, 1, 0, 0, 0, 1068, 1069, 3, 150, 75, 0, 1069, 1071, 5, 126, 0, 0, 1070, 1072, 3, 132, 66, 0, 1071, 1070, 1, 0, 0, 0, 1071, 1072, 1, 0, 0, 0, 1072, 1073, 1, 0, 0, 0, 1073, 1074, 5, 144, 0, 0, 1074, 129, 1, 0, 0, 0, 1075, 1076, 3, 134, 67, 0, 1076, 1077, 5, 116, 0, 0, 1077, 1079, 1, 0, 0, 0, 1078, 1075, 1, 0, 0, 0, 1078, 1079, 1, 0, 0, 0, 1079, 1080, 1, 0, 0, 0, 1080, 1081, 3, 150, 75, 0, 1081, 131, 1, 0, 0, 0, 1082, 1087, 3, 106, 53, 0, 1083, 1084, 5, 112, 0, 0, 1084, 1086, 3, 106, 53, 0, 1085, 1083, 1, 0, 0, 0, 1086, 1089, 1, 0, 0, 0, 1087, 1085, 1, 0, 0, 0, 1087, 1088, 1, 0, 0, 0, 1088, 133, 1, 0, 0, 0, 1089, 1087, 1, 0, 0, 0, 1090, 1091, 3, 150, 75, 0, 1091, 135, 1, 0, 0, 0, 1092, 1101, 5, 102, 0, 0, 1093, 1094, 5, 116, 0, 0, 1094, 1101, 7, 11, 0, 0, 1095, 1096, 5, 104, 0, 0, 1096, 1098, 5, 116, 0, 0, 1097, 1099, 7, 11, 0, 0, 1098, 1097, 1, 0, 0, 0, 1098, 1099, 1, 0, 0, 0, 1099, 1101, 1, 0, 0, 0, 1100, 1092, 1, 0, 0, 0, 1100, 1093, 1, 0, 0, 0, 1100, 1095, 1, 0, 0, 0, 1101, 137, 1, 0, 0, 0, 1102, 1104, 7, 12, 0, 0, 1103, 1102, 1, 0, 0, 0, 1103, 1104, 1, 0, 0, 0, 1104, 1111, 1, 0, 0, 0, 1105, 1112, 3, 136, 68, 0, 1106, 1112, 5, 103, 0, 0, 1107, 1112, 5, 104, 0, 0, 1108, 1112, 5, 105, 0, 0, 1109, 1112, 5, 41, 0, 0, 1110, 1112, 5, 55, 0, 0, 1111, 1105, 1, 0, 0, 0, 1111, 1106, 1, 0, 0, 0, 1111, 1107, 1, 0, 0, 0, 1111, 1108, 1, 0, 0, 0, 1111, 1109, 1, 0, 0, 0, 1111, 1110, 1, 0, 0, 0, 1112, 139, 1, 0, 0, 0, 1113, 1117, 3, 138, 69, 0, 1114, 1117, 5, 106, 0, 0, 1115, 1117, 5, 57, 0, 0, 1116, 1113, 1, 0, 0, 0, 1116, 1114, 1, 0, 0, 0, 1116, 1115, 1, 0, 0, 0, 1117, 141, 1, 0, 0, 0, 1118, 1119, 7, 13, 0, 0, 1119, 143, 1, 0, 0, 0, 1120, 1121, 7, 14, 0, 0, 1121, 145, 1, 0, 0, 0, 1122, 1123, 7, 15, 0, 0, 1123, 147, 1, 0, 0, 0, 1124, 1127, 5, 101, 0, 0, 1125, 1127, 3, 146, 73, 0, 1126, 1124, 1, 0, 0, 0, 1126, 1125, 1, 0, 0, 0, 1127, 149, 1, 0, 0, 0, 1128, 1132, 5, 101, 0, 0, 1129, 1132, 3, 142, 71, 0, 1130, 1132, 3, 144, 72, 0, 1131, 1128, 1, 0, 0, 0, 1131, 1129, 1, 0, 0, 0, 1131, 1130, 1, 0, 0, 0, 1132, 151, 1, 0, 0, 0, 1133, 1134, 3, 156, 78, 0, 1134, 1135, 5, 118, 0, 0, 1135, 1136, 3, 138, 69, 0, 1136, 153, 1, 0, 0, 0, 1137, 1138, 5, 124, 0, 0, 1138, 1139, 3, 150, 75, 0, 1139, 1140, 5, 142, 0, 0, 1140, 155, 1, 0, 0, 0, 1141, 1144, 5, 106, 0, 0, 1142, 1144, 3, 158, 79, 0, 1143, 1141, 1, 0, 0, 0, 1143, 1142, 1, 0, 0, 0, 1144, 157, 1, 0, 0, 0, 1145, 1149, 5, 137, 0, 0, 1146, 1148, 3, 160, 80, 0, 1147, 1146, 1, 0, 0, 0, 1148, 1151, 1, 0, 0, 0, 1149, 1147, 1, 0, 0, 0, 1149, 1150, 1, 0, 0, 0, 1150, 1152, 1, 0, 0, 0, 1151, 1149, 1, 0, 0, 0, 1152, 1153, 5, 139, 0, 0, 1153, 159, 1, 0, 0, 0, 1154, 1155, 5, 152, 0, 0, 1155, 1156, 3, 106, 53, 0, 1156, 1157, 5, 142, 0, 0, 1157, 1160, 1, 0, 0, 0, 1158, 1160, 5, 151, 0, 0, 1159, 1154, 1, 0, 0, 0, 1159, 1158, 1, 0, 0, 0, 1160, 161, 1, 0, 0, 0, 1161, 1165, 5, 138, 0, 0, 1162, 1164, 3, 164, 82, 0, 1163, 1162, 1, 0, 0, 0, 1164, 1167, 1, 0, 0, 0, 1165, 1163, 1, 0, 0, 0, 1165, 1166, 1, 0, 0, 0, 1166, 1168, 1, 0, 0, 0, 1167, 1165, 1, 0, 0, 0, 1168, 1169, 5, 0, 0, 1, 1169, 163, 1, 0, 0, 0, 1170, 1171, 5, 154, 0, 0, 1171, 1172, 3, 106, 53, 0, 1172, 1173, 5, 142, 0, 0, 1173, 1176, 1, 0, 0, 0, 1174, 1176, 5, 153, 0, 0, 1175, 1170, 1, 0, 0, 0, 1175, 1174, 1, 0, 0, 0, 1176, 165, 1, 0, 0, 0, 141, 169, 176, 185, 200, 212, 224, 240, 251, 265, 271, 281, 290, 293, 297, 300, 304, 307, 310, 313, 316, 320, 324, 327, 330, 333, 337, 340, 349, 355, 376, 393, 410, 416, 422, 433, 435, 446, 449, 455, 463, 469, 471, 475, 480, 483, 486, 490, 494, 497, 499, 502, 506, 510, 513, 515, 517, 522, 533, 539, 546, 551, 555, 559, 565, 567, 574, 582, 585, 588, 607, 621, 637, 649, 661, 669, 673, 680, 686, 695, 699, 723, 740, 746, 749, 752, 762, 768, 771, 774, 782, 785, 789, 792, 806, 823, 828, 832, 838, 845, 857, 861, 864, 873, 887, 914, 923, 925, 927, 935, 940, 948, 958, 961, 971, 982, 987, 994, 1007, 1014, 1027, 1033, 1036, 1043, 1055, 1061, 1065, 1071, 1078, 1087, 1098, 1100, 1103, 1111, 1116, 1126, 1131, 1143, 1149, 1159, 1165, 1175] \ No newline at end of file diff --git a/posthog/hogql/grammar/HogQLParser.py b/posthog/hogql/grammar/HogQLParser.py index 50e81765f514d..9c0c50c0835da 100644 --- a/posthog/hogql/grammar/HogQLParser.py +++ b/posthog/hogql/grammar/HogQLParser.py @@ -10,7 +10,7 @@ def serializedATN(): return [ - 4,1,154,1158,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, + 4,1,154,1178,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, @@ -72,74 +72,76 @@ def serializedATN(): 53,700,8,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1, 53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,3,53,724, 8,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53, - 1,53,1,53,1,53,3,53,741,8,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53, - 1,53,1,53,1,53,3,53,753,8,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53, - 1,53,3,53,763,8,53,1,53,3,53,766,8,53,1,53,1,53,3,53,770,8,53,1, - 53,3,53,773,8,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1, - 53,1,53,1,53,3,53,787,8,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1, - 53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,3,53,804,8,53,1,53,1,53,1, - 53,3,53,809,8,53,1,53,1,53,3,53,813,8,53,1,53,1,53,1,53,1,53,3,53, - 819,8,53,1,53,1,53,1,53,1,53,1,53,3,53,826,8,53,1,53,1,53,1,53,1, - 53,1,53,1,53,1,53,1,53,1,53,1,53,3,53,838,8,53,1,53,1,53,3,53,842, - 8,53,1,53,3,53,845,8,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,3,53, - 854,8,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53, - 1,53,3,53,868,8,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53, + 1,53,1,53,1,53,3,53,741,8,53,1,53,1,53,1,53,1,53,3,53,747,8,53,1, + 53,3,53,750,8,53,1,53,3,53,753,8,53,1,53,1,53,1,53,1,53,1,53,1,53, + 1,53,1,53,3,53,763,8,53,1,53,1,53,1,53,1,53,3,53,769,8,53,1,53,3, + 53,772,8,53,1,53,3,53,775,8,53,1,53,1,53,1,53,1,53,1,53,1,53,3,53, + 783,8,53,1,53,3,53,786,8,53,1,53,1,53,3,53,790,8,53,1,53,3,53,793, + 8,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53, + 3,53,807,8,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53, + 1,53,1,53,1,53,1,53,1,53,3,53,824,8,53,1,53,1,53,1,53,3,53,829,8, + 53,1,53,1,53,3,53,833,8,53,1,53,1,53,1,53,1,53,3,53,839,8,53,1,53, + 1,53,1,53,1,53,1,53,3,53,846,8,53,1,53,1,53,1,53,1,53,1,53,1,53, + 1,53,1,53,1,53,1,53,3,53,858,8,53,1,53,1,53,3,53,862,8,53,1,53,3, + 53,865,8,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,3,53,874,8,53,1,53, + 1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,3,53,888, + 8,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53, 1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53, - 1,53,1,53,1,53,3,53,895,8,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53, - 3,53,904,8,53,5,53,906,8,53,10,53,12,53,909,9,53,1,54,1,54,1,54, - 5,54,914,8,54,10,54,12,54,917,9,54,1,55,1,55,3,55,921,8,55,1,56, - 1,56,1,56,1,56,5,56,927,8,56,10,56,12,56,930,9,56,1,56,1,56,1,56, - 1,56,1,56,5,56,937,8,56,10,56,12,56,940,9,56,3,56,942,8,56,1,56, - 1,56,1,56,1,57,1,57,1,57,5,57,950,8,57,10,57,12,57,953,9,57,1,57, - 1,57,1,57,1,57,1,57,1,57,5,57,961,8,57,10,57,12,57,964,9,57,1,57, - 1,57,3,57,968,8,57,1,57,1,57,1,57,1,57,1,57,3,57,975,8,57,1,58,1, - 58,1,58,1,58,1,58,1,58,1,58,1,58,1,58,1,58,1,58,3,58,988,8,58,1, - 59,1,59,1,59,5,59,993,8,59,10,59,12,59,996,9,59,1,60,1,60,1,60,1, - 60,1,60,1,60,1,60,1,60,1,60,1,60,3,60,1008,8,60,1,61,1,61,1,61,1, - 61,3,61,1014,8,61,1,61,3,61,1017,8,61,1,62,1,62,1,62,5,62,1022,8, - 62,10,62,12,62,1025,9,62,1,63,1,63,1,63,1,63,1,63,1,63,1,63,1,63, - 1,63,3,63,1036,8,63,1,63,1,63,1,63,1,63,3,63,1042,8,63,5,63,1044, - 8,63,10,63,12,63,1047,9,63,1,64,1,64,1,64,3,64,1052,8,64,1,64,1, - 64,1,65,1,65,1,65,3,65,1059,8,65,1,65,1,65,1,66,1,66,1,66,5,66,1066, - 8,66,10,66,12,66,1069,9,66,1,67,1,67,1,68,1,68,1,68,1,68,1,68,1, - 68,3,68,1079,8,68,3,68,1081,8,68,1,69,3,69,1084,8,69,1,69,1,69,1, - 69,1,69,1,69,1,69,3,69,1092,8,69,1,70,1,70,1,70,3,70,1097,8,70,1, - 71,1,71,1,72,1,72,1,73,1,73,1,74,1,74,3,74,1107,8,74,1,75,1,75,1, - 75,3,75,1112,8,75,1,76,1,76,1,76,1,76,1,77,1,77,1,77,1,77,1,78,1, - 78,3,78,1124,8,78,1,79,1,79,5,79,1128,8,79,10,79,12,79,1131,9,79, - 1,79,1,79,1,80,1,80,1,80,1,80,1,80,3,80,1140,8,80,1,81,1,81,5,81, - 1144,8,81,10,81,12,81,1147,9,81,1,81,1,81,1,82,1,82,1,82,1,82,1, - 82,3,82,1156,8,82,1,82,0,3,68,106,126,83,0,2,4,6,8,10,12,14,16,18, - 20,22,24,26,28,30,32,34,36,38,40,42,44,46,48,50,52,54,56,58,60,62, - 64,66,68,70,72,74,76,78,80,82,84,86,88,90,92,94,96,98,100,102,104, - 106,108,110,112,114,116,118,120,122,124,126,128,130,132,134,136, - 138,140,142,144,146,148,150,152,154,156,158,160,162,164,0,16,2,0, - 17,17,72,72,2,0,42,42,49,49,3,0,1,1,4,4,8,8,4,0,1,1,3,4,8,8,78,78, - 2,0,49,49,71,71,2,0,1,1,4,4,2,0,7,7,21,22,2,0,28,28,47,47,2,0,69, - 69,74,74,3,0,10,10,48,48,87,87,2,0,39,39,51,51,1,0,103,104,2,0,114, - 114,134,134,7,0,20,20,36,36,53,54,68,68,76,76,93,93,99,99,12,0,1, - 19,21,28,30,35,37,40,42,49,51,52,56,56,58,67,69,75,77,92,94,95,97, - 98,4,0,19,19,28,28,37,37,46,46,1288,0,169,1,0,0,0,2,176,1,0,0,0, - 4,178,1,0,0,0,6,180,1,0,0,0,8,189,1,0,0,0,10,195,1,0,0,0,12,212, - 1,0,0,0,14,214,1,0,0,0,16,217,1,0,0,0,18,226,1,0,0,0,20,232,1,0, - 0,0,22,236,1,0,0,0,24,245,1,0,0,0,26,247,1,0,0,0,28,256,1,0,0,0, - 30,260,1,0,0,0,32,271,1,0,0,0,34,275,1,0,0,0,36,290,1,0,0,0,38,293, - 1,0,0,0,40,342,1,0,0,0,42,345,1,0,0,0,44,351,1,0,0,0,46,355,1,0, - 0,0,48,361,1,0,0,0,50,379,1,0,0,0,52,382,1,0,0,0,54,385,1,0,0,0, - 56,395,1,0,0,0,58,398,1,0,0,0,60,402,1,0,0,0,62,435,1,0,0,0,64,437, - 1,0,0,0,66,440,1,0,0,0,68,455,1,0,0,0,70,517,1,0,0,0,72,522,1,0, - 0,0,74,533,1,0,0,0,76,535,1,0,0,0,78,541,1,0,0,0,80,549,1,0,0,0, - 82,567,1,0,0,0,84,569,1,0,0,0,86,577,1,0,0,0,88,582,1,0,0,0,90,590, - 1,0,0,0,92,594,1,0,0,0,94,598,1,0,0,0,96,607,1,0,0,0,98,621,1,0, - 0,0,100,623,1,0,0,0,102,673,1,0,0,0,104,675,1,0,0,0,106,812,1,0, - 0,0,108,910,1,0,0,0,110,920,1,0,0,0,112,941,1,0,0,0,114,974,1,0, - 0,0,116,987,1,0,0,0,118,989,1,0,0,0,120,1007,1,0,0,0,122,1016,1, - 0,0,0,124,1018,1,0,0,0,126,1035,1,0,0,0,128,1048,1,0,0,0,130,1058, - 1,0,0,0,132,1062,1,0,0,0,134,1070,1,0,0,0,136,1080,1,0,0,0,138,1083, - 1,0,0,0,140,1096,1,0,0,0,142,1098,1,0,0,0,144,1100,1,0,0,0,146,1102, - 1,0,0,0,148,1106,1,0,0,0,150,1111,1,0,0,0,152,1113,1,0,0,0,154,1117, - 1,0,0,0,156,1123,1,0,0,0,158,1125,1,0,0,0,160,1139,1,0,0,0,162,1141, - 1,0,0,0,164,1155,1,0,0,0,166,168,3,2,1,0,167,166,1,0,0,0,168,171, + 3,53,915,8,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,3,53,924,8,53,5, + 53,926,8,53,10,53,12,53,929,9,53,1,54,1,54,1,54,5,54,934,8,54,10, + 54,12,54,937,9,54,1,55,1,55,3,55,941,8,55,1,56,1,56,1,56,1,56,5, + 56,947,8,56,10,56,12,56,950,9,56,1,56,1,56,1,56,1,56,1,56,5,56,957, + 8,56,10,56,12,56,960,9,56,3,56,962,8,56,1,56,1,56,1,56,1,57,1,57, + 1,57,5,57,970,8,57,10,57,12,57,973,9,57,1,57,1,57,1,57,1,57,1,57, + 1,57,5,57,981,8,57,10,57,12,57,984,9,57,1,57,1,57,3,57,988,8,57, + 1,57,1,57,1,57,1,57,1,57,3,57,995,8,57,1,58,1,58,1,58,1,58,1,58, + 1,58,1,58,1,58,1,58,1,58,1,58,3,58,1008,8,58,1,59,1,59,1,59,5,59, + 1013,8,59,10,59,12,59,1016,9,59,1,60,1,60,1,60,1,60,1,60,1,60,1, + 60,1,60,1,60,1,60,3,60,1028,8,60,1,61,1,61,1,61,1,61,3,61,1034,8, + 61,1,61,3,61,1037,8,61,1,62,1,62,1,62,5,62,1042,8,62,10,62,12,62, + 1045,9,62,1,63,1,63,1,63,1,63,1,63,1,63,1,63,1,63,1,63,3,63,1056, + 8,63,1,63,1,63,1,63,1,63,3,63,1062,8,63,5,63,1064,8,63,10,63,12, + 63,1067,9,63,1,64,1,64,1,64,3,64,1072,8,64,1,64,1,64,1,65,1,65,1, + 65,3,65,1079,8,65,1,65,1,65,1,66,1,66,1,66,5,66,1086,8,66,10,66, + 12,66,1089,9,66,1,67,1,67,1,68,1,68,1,68,1,68,1,68,1,68,3,68,1099, + 8,68,3,68,1101,8,68,1,69,3,69,1104,8,69,1,69,1,69,1,69,1,69,1,69, + 1,69,3,69,1112,8,69,1,70,1,70,1,70,3,70,1117,8,70,1,71,1,71,1,72, + 1,72,1,73,1,73,1,74,1,74,3,74,1127,8,74,1,75,1,75,1,75,3,75,1132, + 8,75,1,76,1,76,1,76,1,76,1,77,1,77,1,77,1,77,1,78,1,78,3,78,1144, + 8,78,1,79,1,79,5,79,1148,8,79,10,79,12,79,1151,9,79,1,79,1,79,1, + 80,1,80,1,80,1,80,1,80,3,80,1160,8,80,1,81,1,81,5,81,1164,8,81,10, + 81,12,81,1167,9,81,1,81,1,81,1,82,1,82,1,82,1,82,1,82,3,82,1176, + 8,82,1,82,0,3,68,106,126,83,0,2,4,6,8,10,12,14,16,18,20,22,24,26, + 28,30,32,34,36,38,40,42,44,46,48,50,52,54,56,58,60,62,64,66,68,70, + 72,74,76,78,80,82,84,86,88,90,92,94,96,98,100,102,104,106,108,110, + 112,114,116,118,120,122,124,126,128,130,132,134,136,138,140,142, + 144,146,148,150,152,154,156,158,160,162,164,0,16,2,0,17,17,72,72, + 2,0,42,42,49,49,3,0,1,1,4,4,8,8,4,0,1,1,3,4,8,8,78,78,2,0,49,49, + 71,71,2,0,1,1,4,4,2,0,7,7,21,22,2,0,28,28,47,47,2,0,69,69,74,74, + 3,0,10,10,48,48,87,87,2,0,39,39,51,51,1,0,103,104,2,0,114,114,134, + 134,7,0,20,20,36,36,53,54,68,68,76,76,93,93,99,99,12,0,1,19,21,28, + 30,35,37,40,42,49,51,52,56,56,58,67,69,75,77,92,94,95,97,98,4,0, + 19,19,28,28,37,37,46,46,1314,0,169,1,0,0,0,2,176,1,0,0,0,4,178,1, + 0,0,0,6,180,1,0,0,0,8,189,1,0,0,0,10,195,1,0,0,0,12,212,1,0,0,0, + 14,214,1,0,0,0,16,217,1,0,0,0,18,226,1,0,0,0,20,232,1,0,0,0,22,236, + 1,0,0,0,24,245,1,0,0,0,26,247,1,0,0,0,28,256,1,0,0,0,30,260,1,0, + 0,0,32,271,1,0,0,0,34,275,1,0,0,0,36,290,1,0,0,0,38,293,1,0,0,0, + 40,342,1,0,0,0,42,345,1,0,0,0,44,351,1,0,0,0,46,355,1,0,0,0,48,361, + 1,0,0,0,50,379,1,0,0,0,52,382,1,0,0,0,54,385,1,0,0,0,56,395,1,0, + 0,0,58,398,1,0,0,0,60,402,1,0,0,0,62,435,1,0,0,0,64,437,1,0,0,0, + 66,440,1,0,0,0,68,455,1,0,0,0,70,517,1,0,0,0,72,522,1,0,0,0,74,533, + 1,0,0,0,76,535,1,0,0,0,78,541,1,0,0,0,80,549,1,0,0,0,82,567,1,0, + 0,0,84,569,1,0,0,0,86,577,1,0,0,0,88,582,1,0,0,0,90,590,1,0,0,0, + 92,594,1,0,0,0,94,598,1,0,0,0,96,607,1,0,0,0,98,621,1,0,0,0,100, + 623,1,0,0,0,102,673,1,0,0,0,104,675,1,0,0,0,106,832,1,0,0,0,108, + 930,1,0,0,0,110,940,1,0,0,0,112,961,1,0,0,0,114,994,1,0,0,0,116, + 1007,1,0,0,0,118,1009,1,0,0,0,120,1027,1,0,0,0,122,1036,1,0,0,0, + 124,1038,1,0,0,0,126,1055,1,0,0,0,128,1068,1,0,0,0,130,1078,1,0, + 0,0,132,1082,1,0,0,0,134,1090,1,0,0,0,136,1100,1,0,0,0,138,1103, + 1,0,0,0,140,1116,1,0,0,0,142,1118,1,0,0,0,144,1120,1,0,0,0,146,1122, + 1,0,0,0,148,1126,1,0,0,0,150,1131,1,0,0,0,152,1133,1,0,0,0,154,1137, + 1,0,0,0,156,1143,1,0,0,0,158,1145,1,0,0,0,160,1159,1,0,0,0,162,1161, + 1,0,0,0,164,1175,1,0,0,0,166,168,3,2,1,0,167,166,1,0,0,0,168,171, 1,0,0,0,169,167,1,0,0,0,169,170,1,0,0,0,170,172,1,0,0,0,171,169, 1,0,0,0,172,173,5,0,0,1,173,1,1,0,0,0,174,177,3,6,3,0,175,177,3, 12,6,0,176,174,1,0,0,0,176,175,1,0,0,0,177,3,1,0,0,0,178,179,3,106, @@ -302,170 +304,178 @@ def serializedATN(): 690,3,106,53,0,690,691,5,81,0,0,691,692,3,106,53,0,692,694,1,0,0, 0,693,688,1,0,0,0,694,695,1,0,0,0,695,693,1,0,0,0,695,696,1,0,0, 0,696,699,1,0,0,0,697,698,5,24,0,0,698,700,3,106,53,0,699,697,1, - 0,0,0,699,700,1,0,0,0,700,701,1,0,0,0,701,702,5,25,0,0,702,813,1, + 0,0,0,699,700,1,0,0,0,700,701,1,0,0,0,701,702,5,25,0,0,702,833,1, 0,0,0,703,704,5,13,0,0,704,705,5,126,0,0,705,706,3,106,53,0,706, - 707,5,6,0,0,707,708,3,102,51,0,708,709,5,144,0,0,709,813,1,0,0,0, - 710,711,5,19,0,0,711,813,5,106,0,0,712,713,5,43,0,0,713,714,3,106, - 53,0,714,715,3,142,71,0,715,813,1,0,0,0,716,717,5,80,0,0,717,718, + 707,5,6,0,0,707,708,3,102,51,0,708,709,5,144,0,0,709,833,1,0,0,0, + 710,711,5,19,0,0,711,833,5,106,0,0,712,713,5,43,0,0,713,714,3,106, + 53,0,714,715,3,142,71,0,715,833,1,0,0,0,716,717,5,80,0,0,717,718, 5,126,0,0,718,719,3,106,53,0,719,720,5,32,0,0,720,723,3,106,53,0, 721,722,5,31,0,0,722,724,3,106,53,0,723,721,1,0,0,0,723,724,1,0, - 0,0,724,725,1,0,0,0,725,726,5,144,0,0,726,813,1,0,0,0,727,728,5, - 83,0,0,728,813,5,106,0,0,729,730,5,88,0,0,730,731,5,126,0,0,731, + 0,0,724,725,1,0,0,0,725,726,5,144,0,0,726,833,1,0,0,0,727,728,5, + 83,0,0,728,833,5,106,0,0,729,730,5,88,0,0,730,731,5,126,0,0,731, 732,7,9,0,0,732,733,3,156,78,0,733,734,5,32,0,0,734,735,3,106,53, - 0,735,736,5,144,0,0,736,813,1,0,0,0,737,738,3,150,75,0,738,740,5, + 0,735,736,5,144,0,0,736,833,1,0,0,0,737,738,3,150,75,0,738,740,5, 126,0,0,739,741,3,104,52,0,740,739,1,0,0,0,740,741,1,0,0,0,741,742, - 1,0,0,0,742,743,5,144,0,0,743,744,1,0,0,0,744,745,5,64,0,0,745,746, - 5,126,0,0,746,747,3,88,44,0,747,748,5,144,0,0,748,813,1,0,0,0,749, - 750,3,150,75,0,750,752,5,126,0,0,751,753,3,104,52,0,752,751,1,0, - 0,0,752,753,1,0,0,0,753,754,1,0,0,0,754,755,5,144,0,0,755,756,1, - 0,0,0,756,757,5,64,0,0,757,758,3,150,75,0,758,813,1,0,0,0,759,765, - 3,150,75,0,760,762,5,126,0,0,761,763,3,104,52,0,762,761,1,0,0,0, - 762,763,1,0,0,0,763,764,1,0,0,0,764,766,5,144,0,0,765,760,1,0,0, - 0,765,766,1,0,0,0,766,767,1,0,0,0,767,769,5,126,0,0,768,770,5,23, - 0,0,769,768,1,0,0,0,769,770,1,0,0,0,770,772,1,0,0,0,771,773,3,108, - 54,0,772,771,1,0,0,0,772,773,1,0,0,0,773,774,1,0,0,0,774,775,5,144, - 0,0,775,813,1,0,0,0,776,813,3,114,57,0,777,813,3,158,79,0,778,813, - 3,140,70,0,779,780,5,114,0,0,780,813,3,106,53,19,781,782,5,56,0, - 0,782,813,3,106,53,13,783,784,3,130,65,0,784,785,5,116,0,0,785,787, - 1,0,0,0,786,783,1,0,0,0,786,787,1,0,0,0,787,788,1,0,0,0,788,813, - 5,108,0,0,789,790,5,126,0,0,790,791,3,34,17,0,791,792,5,144,0,0, - 792,813,1,0,0,0,793,794,5,126,0,0,794,795,3,106,53,0,795,796,5,144, - 0,0,796,813,1,0,0,0,797,798,5,126,0,0,798,799,3,104,52,0,799,800, - 5,144,0,0,800,813,1,0,0,0,801,803,5,125,0,0,802,804,3,104,52,0,803, - 802,1,0,0,0,803,804,1,0,0,0,804,805,1,0,0,0,805,813,5,143,0,0,806, - 808,5,124,0,0,807,809,3,30,15,0,808,807,1,0,0,0,808,809,1,0,0,0, - 809,810,1,0,0,0,810,813,5,142,0,0,811,813,3,122,61,0,812,683,1,0, - 0,0,812,703,1,0,0,0,812,710,1,0,0,0,812,712,1,0,0,0,812,716,1,0, - 0,0,812,727,1,0,0,0,812,729,1,0,0,0,812,737,1,0,0,0,812,749,1,0, - 0,0,812,759,1,0,0,0,812,776,1,0,0,0,812,777,1,0,0,0,812,778,1,0, - 0,0,812,779,1,0,0,0,812,781,1,0,0,0,812,786,1,0,0,0,812,789,1,0, - 0,0,812,793,1,0,0,0,812,797,1,0,0,0,812,801,1,0,0,0,812,806,1,0, - 0,0,812,811,1,0,0,0,813,907,1,0,0,0,814,818,10,18,0,0,815,819,5, - 108,0,0,816,819,5,146,0,0,817,819,5,133,0,0,818,815,1,0,0,0,818, - 816,1,0,0,0,818,817,1,0,0,0,819,820,1,0,0,0,820,906,3,106,53,19, - 821,825,10,17,0,0,822,826,5,134,0,0,823,826,5,114,0,0,824,826,5, - 113,0,0,825,822,1,0,0,0,825,823,1,0,0,0,825,824,1,0,0,0,826,827, - 1,0,0,0,827,906,3,106,53,18,828,853,10,16,0,0,829,854,5,117,0,0, - 830,854,5,118,0,0,831,854,5,129,0,0,832,854,5,127,0,0,833,854,5, - 128,0,0,834,854,5,119,0,0,835,854,5,120,0,0,836,838,5,56,0,0,837, - 836,1,0,0,0,837,838,1,0,0,0,838,839,1,0,0,0,839,841,5,40,0,0,840, - 842,5,14,0,0,841,840,1,0,0,0,841,842,1,0,0,0,842,854,1,0,0,0,843, - 845,5,56,0,0,844,843,1,0,0,0,844,845,1,0,0,0,845,846,1,0,0,0,846, - 854,7,10,0,0,847,854,5,140,0,0,848,854,5,141,0,0,849,854,5,131,0, - 0,850,854,5,122,0,0,851,854,5,123,0,0,852,854,5,130,0,0,853,829, - 1,0,0,0,853,830,1,0,0,0,853,831,1,0,0,0,853,832,1,0,0,0,853,833, - 1,0,0,0,853,834,1,0,0,0,853,835,1,0,0,0,853,837,1,0,0,0,853,844, - 1,0,0,0,853,847,1,0,0,0,853,848,1,0,0,0,853,849,1,0,0,0,853,850, - 1,0,0,0,853,851,1,0,0,0,853,852,1,0,0,0,854,855,1,0,0,0,855,906, - 3,106,53,17,856,857,10,14,0,0,857,858,5,132,0,0,858,906,3,106,53, - 15,859,860,10,12,0,0,860,861,5,2,0,0,861,906,3,106,53,13,862,863, - 10,11,0,0,863,864,5,61,0,0,864,906,3,106,53,12,865,867,10,10,0,0, - 866,868,5,56,0,0,867,866,1,0,0,0,867,868,1,0,0,0,868,869,1,0,0,0, - 869,870,5,9,0,0,870,871,3,106,53,0,871,872,5,2,0,0,872,873,3,106, - 53,11,873,906,1,0,0,0,874,875,10,9,0,0,875,876,5,135,0,0,876,877, - 3,106,53,0,877,878,5,111,0,0,878,879,3,106,53,9,879,906,1,0,0,0, - 880,881,10,22,0,0,881,882,5,125,0,0,882,883,3,106,53,0,883,884,5, - 143,0,0,884,906,1,0,0,0,885,886,10,21,0,0,886,887,5,116,0,0,887, - 906,5,104,0,0,888,889,10,20,0,0,889,890,5,116,0,0,890,906,3,150, - 75,0,891,892,10,15,0,0,892,894,5,44,0,0,893,895,5,56,0,0,894,893, - 1,0,0,0,894,895,1,0,0,0,895,896,1,0,0,0,896,906,5,57,0,0,897,903, - 10,8,0,0,898,904,3,148,74,0,899,900,5,6,0,0,900,904,3,150,75,0,901, - 902,5,6,0,0,902,904,5,106,0,0,903,898,1,0,0,0,903,899,1,0,0,0,903, - 901,1,0,0,0,904,906,1,0,0,0,905,814,1,0,0,0,905,821,1,0,0,0,905, - 828,1,0,0,0,905,856,1,0,0,0,905,859,1,0,0,0,905,862,1,0,0,0,905, - 865,1,0,0,0,905,874,1,0,0,0,905,880,1,0,0,0,905,885,1,0,0,0,905, - 888,1,0,0,0,905,891,1,0,0,0,905,897,1,0,0,0,906,909,1,0,0,0,907, - 905,1,0,0,0,907,908,1,0,0,0,908,107,1,0,0,0,909,907,1,0,0,0,910, - 915,3,110,55,0,911,912,5,112,0,0,912,914,3,110,55,0,913,911,1,0, - 0,0,914,917,1,0,0,0,915,913,1,0,0,0,915,916,1,0,0,0,916,109,1,0, - 0,0,917,915,1,0,0,0,918,921,3,112,56,0,919,921,3,106,53,0,920,918, - 1,0,0,0,920,919,1,0,0,0,921,111,1,0,0,0,922,923,5,126,0,0,923,928, - 3,150,75,0,924,925,5,112,0,0,925,927,3,150,75,0,926,924,1,0,0,0, - 927,930,1,0,0,0,928,926,1,0,0,0,928,929,1,0,0,0,929,931,1,0,0,0, - 930,928,1,0,0,0,931,932,5,144,0,0,932,942,1,0,0,0,933,938,3,150, - 75,0,934,935,5,112,0,0,935,937,3,150,75,0,936,934,1,0,0,0,937,940, - 1,0,0,0,938,936,1,0,0,0,938,939,1,0,0,0,939,942,1,0,0,0,940,938, - 1,0,0,0,941,922,1,0,0,0,941,933,1,0,0,0,942,943,1,0,0,0,943,944, - 5,107,0,0,944,945,3,106,53,0,945,113,1,0,0,0,946,947,5,128,0,0,947, - 951,3,150,75,0,948,950,3,116,58,0,949,948,1,0,0,0,950,953,1,0,0, - 0,951,949,1,0,0,0,951,952,1,0,0,0,952,954,1,0,0,0,953,951,1,0,0, - 0,954,955,5,146,0,0,955,956,5,120,0,0,956,975,1,0,0,0,957,958,5, - 128,0,0,958,962,3,150,75,0,959,961,3,116,58,0,960,959,1,0,0,0,961, - 964,1,0,0,0,962,960,1,0,0,0,962,963,1,0,0,0,963,965,1,0,0,0,964, - 962,1,0,0,0,965,967,5,120,0,0,966,968,3,114,57,0,967,966,1,0,0,0, - 967,968,1,0,0,0,968,969,1,0,0,0,969,970,5,128,0,0,970,971,5,146, - 0,0,971,972,3,150,75,0,972,973,5,120,0,0,973,975,1,0,0,0,974,946, - 1,0,0,0,974,957,1,0,0,0,975,115,1,0,0,0,976,977,3,150,75,0,977,978, - 5,118,0,0,978,979,3,156,78,0,979,988,1,0,0,0,980,981,3,150,75,0, - 981,982,5,118,0,0,982,983,5,124,0,0,983,984,3,106,53,0,984,985,5, - 142,0,0,985,988,1,0,0,0,986,988,3,150,75,0,987,976,1,0,0,0,987,980, - 1,0,0,0,987,986,1,0,0,0,988,117,1,0,0,0,989,994,3,120,60,0,990,991, - 5,112,0,0,991,993,3,120,60,0,992,990,1,0,0,0,993,996,1,0,0,0,994, - 992,1,0,0,0,994,995,1,0,0,0,995,119,1,0,0,0,996,994,1,0,0,0,997, - 998,3,150,75,0,998,999,5,6,0,0,999,1000,5,126,0,0,1000,1001,3,34, - 17,0,1001,1002,5,144,0,0,1002,1008,1,0,0,0,1003,1004,3,106,53,0, - 1004,1005,5,6,0,0,1005,1006,3,150,75,0,1006,1008,1,0,0,0,1007,997, - 1,0,0,0,1007,1003,1,0,0,0,1008,121,1,0,0,0,1009,1017,3,154,77,0, - 1010,1011,3,130,65,0,1011,1012,5,116,0,0,1012,1014,1,0,0,0,1013, - 1010,1,0,0,0,1013,1014,1,0,0,0,1014,1015,1,0,0,0,1015,1017,3,124, - 62,0,1016,1009,1,0,0,0,1016,1013,1,0,0,0,1017,123,1,0,0,0,1018,1023, - 3,150,75,0,1019,1020,5,116,0,0,1020,1022,3,150,75,0,1021,1019,1, - 0,0,0,1022,1025,1,0,0,0,1023,1021,1,0,0,0,1023,1024,1,0,0,0,1024, - 125,1,0,0,0,1025,1023,1,0,0,0,1026,1027,6,63,-1,0,1027,1036,3,130, - 65,0,1028,1036,3,128,64,0,1029,1030,5,126,0,0,1030,1031,3,34,17, - 0,1031,1032,5,144,0,0,1032,1036,1,0,0,0,1033,1036,3,114,57,0,1034, - 1036,3,154,77,0,1035,1026,1,0,0,0,1035,1028,1,0,0,0,1035,1029,1, - 0,0,0,1035,1033,1,0,0,0,1035,1034,1,0,0,0,1036,1045,1,0,0,0,1037, - 1041,10,3,0,0,1038,1042,3,148,74,0,1039,1040,5,6,0,0,1040,1042,3, - 150,75,0,1041,1038,1,0,0,0,1041,1039,1,0,0,0,1042,1044,1,0,0,0,1043, - 1037,1,0,0,0,1044,1047,1,0,0,0,1045,1043,1,0,0,0,1045,1046,1,0,0, - 0,1046,127,1,0,0,0,1047,1045,1,0,0,0,1048,1049,3,150,75,0,1049,1051, - 5,126,0,0,1050,1052,3,132,66,0,1051,1050,1,0,0,0,1051,1052,1,0,0, - 0,1052,1053,1,0,0,0,1053,1054,5,144,0,0,1054,129,1,0,0,0,1055,1056, - 3,134,67,0,1056,1057,5,116,0,0,1057,1059,1,0,0,0,1058,1055,1,0,0, - 0,1058,1059,1,0,0,0,1059,1060,1,0,0,0,1060,1061,3,150,75,0,1061, - 131,1,0,0,0,1062,1067,3,106,53,0,1063,1064,5,112,0,0,1064,1066,3, - 106,53,0,1065,1063,1,0,0,0,1066,1069,1,0,0,0,1067,1065,1,0,0,0,1067, - 1068,1,0,0,0,1068,133,1,0,0,0,1069,1067,1,0,0,0,1070,1071,3,150, - 75,0,1071,135,1,0,0,0,1072,1081,5,102,0,0,1073,1074,5,116,0,0,1074, - 1081,7,11,0,0,1075,1076,5,104,0,0,1076,1078,5,116,0,0,1077,1079, - 7,11,0,0,1078,1077,1,0,0,0,1078,1079,1,0,0,0,1079,1081,1,0,0,0,1080, - 1072,1,0,0,0,1080,1073,1,0,0,0,1080,1075,1,0,0,0,1081,137,1,0,0, - 0,1082,1084,7,12,0,0,1083,1082,1,0,0,0,1083,1084,1,0,0,0,1084,1091, - 1,0,0,0,1085,1092,3,136,68,0,1086,1092,5,103,0,0,1087,1092,5,104, - 0,0,1088,1092,5,105,0,0,1089,1092,5,41,0,0,1090,1092,5,55,0,0,1091, - 1085,1,0,0,0,1091,1086,1,0,0,0,1091,1087,1,0,0,0,1091,1088,1,0,0, - 0,1091,1089,1,0,0,0,1091,1090,1,0,0,0,1092,139,1,0,0,0,1093,1097, - 3,138,69,0,1094,1097,5,106,0,0,1095,1097,5,57,0,0,1096,1093,1,0, - 0,0,1096,1094,1,0,0,0,1096,1095,1,0,0,0,1097,141,1,0,0,0,1098,1099, - 7,13,0,0,1099,143,1,0,0,0,1100,1101,7,14,0,0,1101,145,1,0,0,0,1102, - 1103,7,15,0,0,1103,147,1,0,0,0,1104,1107,5,101,0,0,1105,1107,3,146, - 73,0,1106,1104,1,0,0,0,1106,1105,1,0,0,0,1107,149,1,0,0,0,1108,1112, - 5,101,0,0,1109,1112,3,142,71,0,1110,1112,3,144,72,0,1111,1108,1, - 0,0,0,1111,1109,1,0,0,0,1111,1110,1,0,0,0,1112,151,1,0,0,0,1113, - 1114,3,156,78,0,1114,1115,5,118,0,0,1115,1116,3,138,69,0,1116,153, - 1,0,0,0,1117,1118,5,124,0,0,1118,1119,3,150,75,0,1119,1120,5,142, - 0,0,1120,155,1,0,0,0,1121,1124,5,106,0,0,1122,1124,3,158,79,0,1123, - 1121,1,0,0,0,1123,1122,1,0,0,0,1124,157,1,0,0,0,1125,1129,5,137, - 0,0,1126,1128,3,160,80,0,1127,1126,1,0,0,0,1128,1131,1,0,0,0,1129, - 1127,1,0,0,0,1129,1130,1,0,0,0,1130,1132,1,0,0,0,1131,1129,1,0,0, - 0,1132,1133,5,139,0,0,1133,159,1,0,0,0,1134,1135,5,152,0,0,1135, - 1136,3,106,53,0,1136,1137,5,142,0,0,1137,1140,1,0,0,0,1138,1140, - 5,151,0,0,1139,1134,1,0,0,0,1139,1138,1,0,0,0,1140,161,1,0,0,0,1141, - 1145,5,138,0,0,1142,1144,3,164,82,0,1143,1142,1,0,0,0,1144,1147, - 1,0,0,0,1145,1143,1,0,0,0,1145,1146,1,0,0,0,1146,1148,1,0,0,0,1147, - 1145,1,0,0,0,1148,1149,5,0,0,1,1149,163,1,0,0,0,1150,1151,5,154, - 0,0,1151,1152,3,106,53,0,1152,1153,5,142,0,0,1153,1156,1,0,0,0,1154, - 1156,5,153,0,0,1155,1150,1,0,0,0,1155,1154,1,0,0,0,1156,165,1,0, - 0,0,135,169,176,185,200,212,224,240,251,265,271,281,290,293,297, + 1,0,0,0,742,743,5,144,0,0,743,752,1,0,0,0,744,746,5,126,0,0,745, + 747,5,23,0,0,746,745,1,0,0,0,746,747,1,0,0,0,747,749,1,0,0,0,748, + 750,3,108,54,0,749,748,1,0,0,0,749,750,1,0,0,0,750,751,1,0,0,0,751, + 753,5,144,0,0,752,744,1,0,0,0,752,753,1,0,0,0,753,754,1,0,0,0,754, + 755,5,64,0,0,755,756,5,126,0,0,756,757,3,88,44,0,757,758,5,144,0, + 0,758,833,1,0,0,0,759,760,3,150,75,0,760,762,5,126,0,0,761,763,3, + 104,52,0,762,761,1,0,0,0,762,763,1,0,0,0,763,764,1,0,0,0,764,765, + 5,144,0,0,765,774,1,0,0,0,766,768,5,126,0,0,767,769,5,23,0,0,768, + 767,1,0,0,0,768,769,1,0,0,0,769,771,1,0,0,0,770,772,3,108,54,0,771, + 770,1,0,0,0,771,772,1,0,0,0,772,773,1,0,0,0,773,775,5,144,0,0,774, + 766,1,0,0,0,774,775,1,0,0,0,775,776,1,0,0,0,776,777,5,64,0,0,777, + 778,3,150,75,0,778,833,1,0,0,0,779,785,3,150,75,0,780,782,5,126, + 0,0,781,783,3,104,52,0,782,781,1,0,0,0,782,783,1,0,0,0,783,784,1, + 0,0,0,784,786,5,144,0,0,785,780,1,0,0,0,785,786,1,0,0,0,786,787, + 1,0,0,0,787,789,5,126,0,0,788,790,5,23,0,0,789,788,1,0,0,0,789,790, + 1,0,0,0,790,792,1,0,0,0,791,793,3,108,54,0,792,791,1,0,0,0,792,793, + 1,0,0,0,793,794,1,0,0,0,794,795,5,144,0,0,795,833,1,0,0,0,796,833, + 3,114,57,0,797,833,3,158,79,0,798,833,3,140,70,0,799,800,5,114,0, + 0,800,833,3,106,53,19,801,802,5,56,0,0,802,833,3,106,53,13,803,804, + 3,130,65,0,804,805,5,116,0,0,805,807,1,0,0,0,806,803,1,0,0,0,806, + 807,1,0,0,0,807,808,1,0,0,0,808,833,5,108,0,0,809,810,5,126,0,0, + 810,811,3,34,17,0,811,812,5,144,0,0,812,833,1,0,0,0,813,814,5,126, + 0,0,814,815,3,106,53,0,815,816,5,144,0,0,816,833,1,0,0,0,817,818, + 5,126,0,0,818,819,3,104,52,0,819,820,5,144,0,0,820,833,1,0,0,0,821, + 823,5,125,0,0,822,824,3,104,52,0,823,822,1,0,0,0,823,824,1,0,0,0, + 824,825,1,0,0,0,825,833,5,143,0,0,826,828,5,124,0,0,827,829,3,30, + 15,0,828,827,1,0,0,0,828,829,1,0,0,0,829,830,1,0,0,0,830,833,5,142, + 0,0,831,833,3,122,61,0,832,683,1,0,0,0,832,703,1,0,0,0,832,710,1, + 0,0,0,832,712,1,0,0,0,832,716,1,0,0,0,832,727,1,0,0,0,832,729,1, + 0,0,0,832,737,1,0,0,0,832,759,1,0,0,0,832,779,1,0,0,0,832,796,1, + 0,0,0,832,797,1,0,0,0,832,798,1,0,0,0,832,799,1,0,0,0,832,801,1, + 0,0,0,832,806,1,0,0,0,832,809,1,0,0,0,832,813,1,0,0,0,832,817,1, + 0,0,0,832,821,1,0,0,0,832,826,1,0,0,0,832,831,1,0,0,0,833,927,1, + 0,0,0,834,838,10,18,0,0,835,839,5,108,0,0,836,839,5,146,0,0,837, + 839,5,133,0,0,838,835,1,0,0,0,838,836,1,0,0,0,838,837,1,0,0,0,839, + 840,1,0,0,0,840,926,3,106,53,19,841,845,10,17,0,0,842,846,5,134, + 0,0,843,846,5,114,0,0,844,846,5,113,0,0,845,842,1,0,0,0,845,843, + 1,0,0,0,845,844,1,0,0,0,846,847,1,0,0,0,847,926,3,106,53,18,848, + 873,10,16,0,0,849,874,5,117,0,0,850,874,5,118,0,0,851,874,5,129, + 0,0,852,874,5,127,0,0,853,874,5,128,0,0,854,874,5,119,0,0,855,874, + 5,120,0,0,856,858,5,56,0,0,857,856,1,0,0,0,857,858,1,0,0,0,858,859, + 1,0,0,0,859,861,5,40,0,0,860,862,5,14,0,0,861,860,1,0,0,0,861,862, + 1,0,0,0,862,874,1,0,0,0,863,865,5,56,0,0,864,863,1,0,0,0,864,865, + 1,0,0,0,865,866,1,0,0,0,866,874,7,10,0,0,867,874,5,140,0,0,868,874, + 5,141,0,0,869,874,5,131,0,0,870,874,5,122,0,0,871,874,5,123,0,0, + 872,874,5,130,0,0,873,849,1,0,0,0,873,850,1,0,0,0,873,851,1,0,0, + 0,873,852,1,0,0,0,873,853,1,0,0,0,873,854,1,0,0,0,873,855,1,0,0, + 0,873,857,1,0,0,0,873,864,1,0,0,0,873,867,1,0,0,0,873,868,1,0,0, + 0,873,869,1,0,0,0,873,870,1,0,0,0,873,871,1,0,0,0,873,872,1,0,0, + 0,874,875,1,0,0,0,875,926,3,106,53,17,876,877,10,14,0,0,877,878, + 5,132,0,0,878,926,3,106,53,15,879,880,10,12,0,0,880,881,5,2,0,0, + 881,926,3,106,53,13,882,883,10,11,0,0,883,884,5,61,0,0,884,926,3, + 106,53,12,885,887,10,10,0,0,886,888,5,56,0,0,887,886,1,0,0,0,887, + 888,1,0,0,0,888,889,1,0,0,0,889,890,5,9,0,0,890,891,3,106,53,0,891, + 892,5,2,0,0,892,893,3,106,53,11,893,926,1,0,0,0,894,895,10,9,0,0, + 895,896,5,135,0,0,896,897,3,106,53,0,897,898,5,111,0,0,898,899,3, + 106,53,9,899,926,1,0,0,0,900,901,10,22,0,0,901,902,5,125,0,0,902, + 903,3,106,53,0,903,904,5,143,0,0,904,926,1,0,0,0,905,906,10,21,0, + 0,906,907,5,116,0,0,907,926,5,104,0,0,908,909,10,20,0,0,909,910, + 5,116,0,0,910,926,3,150,75,0,911,912,10,15,0,0,912,914,5,44,0,0, + 913,915,5,56,0,0,914,913,1,0,0,0,914,915,1,0,0,0,915,916,1,0,0,0, + 916,926,5,57,0,0,917,923,10,8,0,0,918,924,3,148,74,0,919,920,5,6, + 0,0,920,924,3,150,75,0,921,922,5,6,0,0,922,924,5,106,0,0,923,918, + 1,0,0,0,923,919,1,0,0,0,923,921,1,0,0,0,924,926,1,0,0,0,925,834, + 1,0,0,0,925,841,1,0,0,0,925,848,1,0,0,0,925,876,1,0,0,0,925,879, + 1,0,0,0,925,882,1,0,0,0,925,885,1,0,0,0,925,894,1,0,0,0,925,900, + 1,0,0,0,925,905,1,0,0,0,925,908,1,0,0,0,925,911,1,0,0,0,925,917, + 1,0,0,0,926,929,1,0,0,0,927,925,1,0,0,0,927,928,1,0,0,0,928,107, + 1,0,0,0,929,927,1,0,0,0,930,935,3,110,55,0,931,932,5,112,0,0,932, + 934,3,110,55,0,933,931,1,0,0,0,934,937,1,0,0,0,935,933,1,0,0,0,935, + 936,1,0,0,0,936,109,1,0,0,0,937,935,1,0,0,0,938,941,3,112,56,0,939, + 941,3,106,53,0,940,938,1,0,0,0,940,939,1,0,0,0,941,111,1,0,0,0,942, + 943,5,126,0,0,943,948,3,150,75,0,944,945,5,112,0,0,945,947,3,150, + 75,0,946,944,1,0,0,0,947,950,1,0,0,0,948,946,1,0,0,0,948,949,1,0, + 0,0,949,951,1,0,0,0,950,948,1,0,0,0,951,952,5,144,0,0,952,962,1, + 0,0,0,953,958,3,150,75,0,954,955,5,112,0,0,955,957,3,150,75,0,956, + 954,1,0,0,0,957,960,1,0,0,0,958,956,1,0,0,0,958,959,1,0,0,0,959, + 962,1,0,0,0,960,958,1,0,0,0,961,942,1,0,0,0,961,953,1,0,0,0,962, + 963,1,0,0,0,963,964,5,107,0,0,964,965,3,106,53,0,965,113,1,0,0,0, + 966,967,5,128,0,0,967,971,3,150,75,0,968,970,3,116,58,0,969,968, + 1,0,0,0,970,973,1,0,0,0,971,969,1,0,0,0,971,972,1,0,0,0,972,974, + 1,0,0,0,973,971,1,0,0,0,974,975,5,146,0,0,975,976,5,120,0,0,976, + 995,1,0,0,0,977,978,5,128,0,0,978,982,3,150,75,0,979,981,3,116,58, + 0,980,979,1,0,0,0,981,984,1,0,0,0,982,980,1,0,0,0,982,983,1,0,0, + 0,983,985,1,0,0,0,984,982,1,0,0,0,985,987,5,120,0,0,986,988,3,114, + 57,0,987,986,1,0,0,0,987,988,1,0,0,0,988,989,1,0,0,0,989,990,5,128, + 0,0,990,991,5,146,0,0,991,992,3,150,75,0,992,993,5,120,0,0,993,995, + 1,0,0,0,994,966,1,0,0,0,994,977,1,0,0,0,995,115,1,0,0,0,996,997, + 3,150,75,0,997,998,5,118,0,0,998,999,3,156,78,0,999,1008,1,0,0,0, + 1000,1001,3,150,75,0,1001,1002,5,118,0,0,1002,1003,5,124,0,0,1003, + 1004,3,106,53,0,1004,1005,5,142,0,0,1005,1008,1,0,0,0,1006,1008, + 3,150,75,0,1007,996,1,0,0,0,1007,1000,1,0,0,0,1007,1006,1,0,0,0, + 1008,117,1,0,0,0,1009,1014,3,120,60,0,1010,1011,5,112,0,0,1011,1013, + 3,120,60,0,1012,1010,1,0,0,0,1013,1016,1,0,0,0,1014,1012,1,0,0,0, + 1014,1015,1,0,0,0,1015,119,1,0,0,0,1016,1014,1,0,0,0,1017,1018,3, + 150,75,0,1018,1019,5,6,0,0,1019,1020,5,126,0,0,1020,1021,3,34,17, + 0,1021,1022,5,144,0,0,1022,1028,1,0,0,0,1023,1024,3,106,53,0,1024, + 1025,5,6,0,0,1025,1026,3,150,75,0,1026,1028,1,0,0,0,1027,1017,1, + 0,0,0,1027,1023,1,0,0,0,1028,121,1,0,0,0,1029,1037,3,154,77,0,1030, + 1031,3,130,65,0,1031,1032,5,116,0,0,1032,1034,1,0,0,0,1033,1030, + 1,0,0,0,1033,1034,1,0,0,0,1034,1035,1,0,0,0,1035,1037,3,124,62,0, + 1036,1029,1,0,0,0,1036,1033,1,0,0,0,1037,123,1,0,0,0,1038,1043,3, + 150,75,0,1039,1040,5,116,0,0,1040,1042,3,150,75,0,1041,1039,1,0, + 0,0,1042,1045,1,0,0,0,1043,1041,1,0,0,0,1043,1044,1,0,0,0,1044,125, + 1,0,0,0,1045,1043,1,0,0,0,1046,1047,6,63,-1,0,1047,1056,3,130,65, + 0,1048,1056,3,128,64,0,1049,1050,5,126,0,0,1050,1051,3,34,17,0,1051, + 1052,5,144,0,0,1052,1056,1,0,0,0,1053,1056,3,114,57,0,1054,1056, + 3,154,77,0,1055,1046,1,0,0,0,1055,1048,1,0,0,0,1055,1049,1,0,0,0, + 1055,1053,1,0,0,0,1055,1054,1,0,0,0,1056,1065,1,0,0,0,1057,1061, + 10,3,0,0,1058,1062,3,148,74,0,1059,1060,5,6,0,0,1060,1062,3,150, + 75,0,1061,1058,1,0,0,0,1061,1059,1,0,0,0,1062,1064,1,0,0,0,1063, + 1057,1,0,0,0,1064,1067,1,0,0,0,1065,1063,1,0,0,0,1065,1066,1,0,0, + 0,1066,127,1,0,0,0,1067,1065,1,0,0,0,1068,1069,3,150,75,0,1069,1071, + 5,126,0,0,1070,1072,3,132,66,0,1071,1070,1,0,0,0,1071,1072,1,0,0, + 0,1072,1073,1,0,0,0,1073,1074,5,144,0,0,1074,129,1,0,0,0,1075,1076, + 3,134,67,0,1076,1077,5,116,0,0,1077,1079,1,0,0,0,1078,1075,1,0,0, + 0,1078,1079,1,0,0,0,1079,1080,1,0,0,0,1080,1081,3,150,75,0,1081, + 131,1,0,0,0,1082,1087,3,106,53,0,1083,1084,5,112,0,0,1084,1086,3, + 106,53,0,1085,1083,1,0,0,0,1086,1089,1,0,0,0,1087,1085,1,0,0,0,1087, + 1088,1,0,0,0,1088,133,1,0,0,0,1089,1087,1,0,0,0,1090,1091,3,150, + 75,0,1091,135,1,0,0,0,1092,1101,5,102,0,0,1093,1094,5,116,0,0,1094, + 1101,7,11,0,0,1095,1096,5,104,0,0,1096,1098,5,116,0,0,1097,1099, + 7,11,0,0,1098,1097,1,0,0,0,1098,1099,1,0,0,0,1099,1101,1,0,0,0,1100, + 1092,1,0,0,0,1100,1093,1,0,0,0,1100,1095,1,0,0,0,1101,137,1,0,0, + 0,1102,1104,7,12,0,0,1103,1102,1,0,0,0,1103,1104,1,0,0,0,1104,1111, + 1,0,0,0,1105,1112,3,136,68,0,1106,1112,5,103,0,0,1107,1112,5,104, + 0,0,1108,1112,5,105,0,0,1109,1112,5,41,0,0,1110,1112,5,55,0,0,1111, + 1105,1,0,0,0,1111,1106,1,0,0,0,1111,1107,1,0,0,0,1111,1108,1,0,0, + 0,1111,1109,1,0,0,0,1111,1110,1,0,0,0,1112,139,1,0,0,0,1113,1117, + 3,138,69,0,1114,1117,5,106,0,0,1115,1117,5,57,0,0,1116,1113,1,0, + 0,0,1116,1114,1,0,0,0,1116,1115,1,0,0,0,1117,141,1,0,0,0,1118,1119, + 7,13,0,0,1119,143,1,0,0,0,1120,1121,7,14,0,0,1121,145,1,0,0,0,1122, + 1123,7,15,0,0,1123,147,1,0,0,0,1124,1127,5,101,0,0,1125,1127,3,146, + 73,0,1126,1124,1,0,0,0,1126,1125,1,0,0,0,1127,149,1,0,0,0,1128,1132, + 5,101,0,0,1129,1132,3,142,71,0,1130,1132,3,144,72,0,1131,1128,1, + 0,0,0,1131,1129,1,0,0,0,1131,1130,1,0,0,0,1132,151,1,0,0,0,1133, + 1134,3,156,78,0,1134,1135,5,118,0,0,1135,1136,3,138,69,0,1136,153, + 1,0,0,0,1137,1138,5,124,0,0,1138,1139,3,150,75,0,1139,1140,5,142, + 0,0,1140,155,1,0,0,0,1141,1144,5,106,0,0,1142,1144,3,158,79,0,1143, + 1141,1,0,0,0,1143,1142,1,0,0,0,1144,157,1,0,0,0,1145,1149,5,137, + 0,0,1146,1148,3,160,80,0,1147,1146,1,0,0,0,1148,1151,1,0,0,0,1149, + 1147,1,0,0,0,1149,1150,1,0,0,0,1150,1152,1,0,0,0,1151,1149,1,0,0, + 0,1152,1153,5,139,0,0,1153,159,1,0,0,0,1154,1155,5,152,0,0,1155, + 1156,3,106,53,0,1156,1157,5,142,0,0,1157,1160,1,0,0,0,1158,1160, + 5,151,0,0,1159,1154,1,0,0,0,1159,1158,1,0,0,0,1160,161,1,0,0,0,1161, + 1165,5,138,0,0,1162,1164,3,164,82,0,1163,1162,1,0,0,0,1164,1167, + 1,0,0,0,1165,1163,1,0,0,0,1165,1166,1,0,0,0,1166,1168,1,0,0,0,1167, + 1165,1,0,0,0,1168,1169,5,0,0,1,1169,163,1,0,0,0,1170,1171,5,154, + 0,0,1171,1172,3,106,53,0,1172,1173,5,142,0,0,1173,1176,1,0,0,0,1174, + 1176,5,153,0,0,1175,1170,1,0,0,0,1175,1174,1,0,0,0,1176,165,1,0, + 0,0,141,169,176,185,200,212,224,240,251,265,271,281,290,293,297, 300,304,307,310,313,316,320,324,327,330,333,337,340,349,355,376, 393,410,416,422,433,435,446,449,455,463,469,471,475,480,483,486, 490,494,497,499,502,506,510,513,515,517,522,533,539,546,551,555, 559,565,567,574,582,585,588,607,621,637,649,661,669,673,680,686, - 695,699,723,740,752,762,765,769,772,786,803,808,812,818,825,837, - 841,844,853,867,894,903,905,907,915,920,928,938,941,951,962,967, - 974,987,994,1007,1013,1016,1023,1035,1041,1045,1051,1058,1067,1078, - 1080,1083,1091,1096,1106,1111,1123,1129,1139,1145,1155 + 695,699,723,740,746,749,752,762,768,771,774,782,785,789,792,806, + 823,828,832,838,845,857,861,864,873,887,914,923,925,927,935,940, + 948,958,961,971,982,987,994,1007,1014,1027,1033,1036,1043,1055,1061, + 1065,1071,1078,1087,1098,1100,1103,1111,1116,1126,1131,1143,1149, + 1159,1165,1175 ] class HogQLParser ( Parser ): @@ -5467,13 +5477,24 @@ def identifier(self, i:int=None): def OVER(self): return self.getToken(HogQLParser.OVER, 0) - def LPAREN(self): - return self.getToken(HogQLParser.LPAREN, 0) - def RPAREN(self): - return self.getToken(HogQLParser.RPAREN, 0) + def LPAREN(self, i:int=None): + if i is None: + return self.getTokens(HogQLParser.LPAREN) + else: + return self.getToken(HogQLParser.LPAREN, i) + def RPAREN(self, i:int=None): + if i is None: + return self.getTokens(HogQLParser.RPAREN) + else: + return self.getToken(HogQLParser.RPAREN, i) def columnExprList(self): return self.getTypedRuleContext(HogQLParser.ColumnExprListContext,0) + def DISTINCT(self): + return self.getToken(HogQLParser.DISTINCT, 0) + def columnArgList(self): + return self.getTypedRuleContext(HogQLParser.ColumnArgListContext,0) + def accept(self, visitor:ParseTreeVisitor): if hasattr( visitor, "visitColumnExprWinFunctionTarget" ): @@ -5851,6 +5872,11 @@ def RPAREN(self, i:int=None): def columnExprList(self): return self.getTypedRuleContext(HogQLParser.ColumnExprListContext,0) + def DISTINCT(self): + return self.getToken(HogQLParser.DISTINCT, 0) + def columnArgList(self): + return self.getTypedRuleContext(HogQLParser.ColumnArgListContext,0) + def accept(self, visitor:ParseTreeVisitor): if hasattr( visitor, "visitColumnExprWinFunction" ): @@ -5943,9 +5969,9 @@ def columnExpr(self, _p:int=0): self._la = 0 # Token type try: self.enterOuterAlt(localctx, 1) - self.state = 812 + self.state = 832 self._errHandler.sync(self) - la_ = self._interp.adaptivePredict(self._input,90,self._ctx) + la_ = self._interp.adaptivePredict(self._input,96,self._ctx) if la_ == 1: localctx = HogQLParser.ColumnExprCaseContext(self, localctx) self._ctx = localctx @@ -6115,13 +6141,39 @@ def columnExpr(self, _p:int=0): self.state = 742 self.match(HogQLParser.RPAREN) - self.state = 744 + self.state = 752 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==126: + self.state = 744 + self.match(HogQLParser.LPAREN) + self.state = 746 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,82,self._ctx) + if la_ == 1: + self.state = 745 + self.match(HogQLParser.DISTINCT) + + + self.state = 749 + self._errHandler.sync(self) + _la = self._input.LA(1) + if (((_la) & ~0x3f) == 0 and ((1 << _la) & -1125900443713538) != 0) or ((((_la - 64)) & ~0x3f) == 0 and ((1 << (_la - 64)) & 8076106347046764543) != 0) or ((((_la - 128)) & ~0x3f) == 0 and ((1 << (_la - 128)) & 577) != 0): + self.state = 748 + self.columnArgList() + + + self.state = 751 + self.match(HogQLParser.RPAREN) + + + self.state = 754 self.match(HogQLParser.OVER) - self.state = 745 + self.state = 755 self.match(HogQLParser.LPAREN) - self.state = 746 + self.state = 756 self.windowExpr() - self.state = 747 + self.state = 757 self.match(HogQLParser.RPAREN) pass @@ -6129,24 +6181,50 @@ def columnExpr(self, _p:int=0): localctx = HogQLParser.ColumnExprWinFunctionTargetContext(self, localctx) self._ctx = localctx _prevctx = localctx - self.state = 749 + self.state = 759 self.identifier() - self.state = 750 + self.state = 760 self.match(HogQLParser.LPAREN) - self.state = 752 + self.state = 762 self._errHandler.sync(self) _la = self._input.LA(1) if (((_la) & ~0x3f) == 0 and ((1 << _la) & -1125900443713538) != 0) or ((((_la - 64)) & ~0x3f) == 0 and ((1 << (_la - 64)) & 8076106347046764543) != 0) or ((((_la - 128)) & ~0x3f) == 0 and ((1 << (_la - 128)) & 577) != 0): - self.state = 751 + self.state = 761 self.columnExprList() - self.state = 754 + self.state = 764 self.match(HogQLParser.RPAREN) - self.state = 756 + self.state = 774 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==126: + self.state = 766 + self.match(HogQLParser.LPAREN) + self.state = 768 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,86,self._ctx) + if la_ == 1: + self.state = 767 + self.match(HogQLParser.DISTINCT) + + + self.state = 771 + self._errHandler.sync(self) + _la = self._input.LA(1) + if (((_la) & ~0x3f) == 0 and ((1 << _la) & -1125900443713538) != 0) or ((((_la - 64)) & ~0x3f) == 0 and ((1 << (_la - 64)) & 8076106347046764543) != 0) or ((((_la - 128)) & ~0x3f) == 0 and ((1 << (_la - 128)) & 577) != 0): + self.state = 770 + self.columnArgList() + + + self.state = 773 + self.match(HogQLParser.RPAREN) + + + self.state = 776 self.match(HogQLParser.OVER) - self.state = 757 + self.state = 777 self.identifier() pass @@ -6154,45 +6232,45 @@ def columnExpr(self, _p:int=0): localctx = HogQLParser.ColumnExprFunctionContext(self, localctx) self._ctx = localctx _prevctx = localctx - self.state = 759 + self.state = 779 self.identifier() - self.state = 765 + self.state = 785 self._errHandler.sync(self) - la_ = self._interp.adaptivePredict(self._input,84,self._ctx) + la_ = self._interp.adaptivePredict(self._input,90,self._ctx) if la_ == 1: - self.state = 760 + self.state = 780 self.match(HogQLParser.LPAREN) - self.state = 762 + self.state = 782 self._errHandler.sync(self) _la = self._input.LA(1) if (((_la) & ~0x3f) == 0 and ((1 << _la) & -1125900443713538) != 0) or ((((_la - 64)) & ~0x3f) == 0 and ((1 << (_la - 64)) & 8076106347046764543) != 0) or ((((_la - 128)) & ~0x3f) == 0 and ((1 << (_la - 128)) & 577) != 0): - self.state = 761 + self.state = 781 self.columnExprList() - self.state = 764 + self.state = 784 self.match(HogQLParser.RPAREN) - self.state = 767 + self.state = 787 self.match(HogQLParser.LPAREN) - self.state = 769 + self.state = 789 self._errHandler.sync(self) - la_ = self._interp.adaptivePredict(self._input,85,self._ctx) + la_ = self._interp.adaptivePredict(self._input,91,self._ctx) if la_ == 1: - self.state = 768 + self.state = 788 self.match(HogQLParser.DISTINCT) - self.state = 772 + self.state = 792 self._errHandler.sync(self) _la = self._input.LA(1) if (((_la) & ~0x3f) == 0 and ((1 << _la) & -1125900443713538) != 0) or ((((_la - 64)) & ~0x3f) == 0 and ((1 << (_la - 64)) & 8076106347046764543) != 0) or ((((_la - 128)) & ~0x3f) == 0 and ((1 << (_la - 128)) & 577) != 0): - self.state = 771 + self.state = 791 self.columnArgList() - self.state = 774 + self.state = 794 self.match(HogQLParser.RPAREN) pass @@ -6200,7 +6278,7 @@ def columnExpr(self, _p:int=0): localctx = HogQLParser.ColumnExprTagElementContext(self, localctx) self._ctx = localctx _prevctx = localctx - self.state = 776 + self.state = 796 self.hogqlxTagElement() pass @@ -6208,7 +6286,7 @@ def columnExpr(self, _p:int=0): localctx = HogQLParser.ColumnExprTemplateStringContext(self, localctx) self._ctx = localctx _prevctx = localctx - self.state = 777 + self.state = 797 self.templateString() pass @@ -6216,7 +6294,7 @@ def columnExpr(self, _p:int=0): localctx = HogQLParser.ColumnExprLiteralContext(self, localctx) self._ctx = localctx _prevctx = localctx - self.state = 778 + self.state = 798 self.literal() pass @@ -6224,9 +6302,9 @@ def columnExpr(self, _p:int=0): localctx = HogQLParser.ColumnExprNegateContext(self, localctx) self._ctx = localctx _prevctx = localctx - self.state = 779 + self.state = 799 self.match(HogQLParser.DASH) - self.state = 780 + self.state = 800 self.columnExpr(19) pass @@ -6234,9 +6312,9 @@ def columnExpr(self, _p:int=0): localctx = HogQLParser.ColumnExprNotContext(self, localctx) self._ctx = localctx _prevctx = localctx - self.state = 781 + self.state = 801 self.match(HogQLParser.NOT) - self.state = 782 + self.state = 802 self.columnExpr(13) pass @@ -6244,17 +6322,17 @@ def columnExpr(self, _p:int=0): localctx = HogQLParser.ColumnExprAsteriskContext(self, localctx) self._ctx = localctx _prevctx = localctx - self.state = 786 + self.state = 806 self._errHandler.sync(self) _la = self._input.LA(1) if (((_la) & ~0x3f) == 0 and ((1 << _la) & -181272084561788930) != 0) or ((((_la - 64)) & ~0x3f) == 0 and ((1 << (_la - 64)) & 201863462911) != 0): - self.state = 783 + self.state = 803 self.tableIdentifier() - self.state = 784 + self.state = 804 self.match(HogQLParser.DOT) - self.state = 788 + self.state = 808 self.match(HogQLParser.ASTERISK) pass @@ -6262,11 +6340,11 @@ def columnExpr(self, _p:int=0): localctx = HogQLParser.ColumnExprSubqueryContext(self, localctx) self._ctx = localctx _prevctx = localctx - self.state = 789 + self.state = 809 self.match(HogQLParser.LPAREN) - self.state = 790 + self.state = 810 self.selectUnionStmt() - self.state = 791 + self.state = 811 self.match(HogQLParser.RPAREN) pass @@ -6274,11 +6352,11 @@ def columnExpr(self, _p:int=0): localctx = HogQLParser.ColumnExprParensContext(self, localctx) self._ctx = localctx _prevctx = localctx - self.state = 793 + self.state = 813 self.match(HogQLParser.LPAREN) - self.state = 794 + self.state = 814 self.columnExpr(0) - self.state = 795 + self.state = 815 self.match(HogQLParser.RPAREN) pass @@ -6286,11 +6364,11 @@ def columnExpr(self, _p:int=0): localctx = HogQLParser.ColumnExprTupleContext(self, localctx) self._ctx = localctx _prevctx = localctx - self.state = 797 + self.state = 817 self.match(HogQLParser.LPAREN) - self.state = 798 + self.state = 818 self.columnExprList() - self.state = 799 + self.state = 819 self.match(HogQLParser.RPAREN) pass @@ -6298,17 +6376,17 @@ def columnExpr(self, _p:int=0): localctx = HogQLParser.ColumnExprArrayContext(self, localctx) self._ctx = localctx _prevctx = localctx - self.state = 801 + self.state = 821 self.match(HogQLParser.LBRACKET) - self.state = 803 + self.state = 823 self._errHandler.sync(self) _la = self._input.LA(1) if (((_la) & ~0x3f) == 0 and ((1 << _la) & -1125900443713538) != 0) or ((((_la - 64)) & ~0x3f) == 0 and ((1 << (_la - 64)) & 8076106347046764543) != 0) or ((((_la - 128)) & ~0x3f) == 0 and ((1 << (_la - 128)) & 577) != 0): - self.state = 802 + self.state = 822 self.columnExprList() - self.state = 805 + self.state = 825 self.match(HogQLParser.RBRACKET) pass @@ -6316,17 +6394,17 @@ def columnExpr(self, _p:int=0): localctx = HogQLParser.ColumnExprDictContext(self, localctx) self._ctx = localctx _prevctx = localctx - self.state = 806 + self.state = 826 self.match(HogQLParser.LBRACE) - self.state = 808 + self.state = 828 self._errHandler.sync(self) _la = self._input.LA(1) if (((_la) & ~0x3f) == 0 and ((1 << _la) & -1125900443713538) != 0) or ((((_la - 64)) & ~0x3f) == 0 and ((1 << (_la - 64)) & 8076106347046764543) != 0) or ((((_la - 128)) & ~0x3f) == 0 and ((1 << (_la - 128)) & 577) != 0): - self.state = 807 + self.state = 827 self.kvPairList() - self.state = 810 + self.state = 830 self.match(HogQLParser.RBRACE) pass @@ -6334,50 +6412,50 @@ def columnExpr(self, _p:int=0): localctx = HogQLParser.ColumnExprIdentifierContext(self, localctx) self._ctx = localctx _prevctx = localctx - self.state = 811 + self.state = 831 self.columnIdentifier() pass self._ctx.stop = self._input.LT(-1) - self.state = 907 + self.state = 927 self._errHandler.sync(self) - _alt = self._interp.adaptivePredict(self._input,101,self._ctx) + _alt = self._interp.adaptivePredict(self._input,107,self._ctx) while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER: if _alt==1: if self._parseListeners is not None: self.triggerExitRuleEvent() _prevctx = localctx - self.state = 905 + self.state = 925 self._errHandler.sync(self) - la_ = self._interp.adaptivePredict(self._input,100,self._ctx) + la_ = self._interp.adaptivePredict(self._input,106,self._ctx) if la_ == 1: localctx = HogQLParser.ColumnExprPrecedence1Context(self, HogQLParser.ColumnExprContext(self, _parentctx, _parentState)) localctx.left = _prevctx self.pushNewRecursionContext(localctx, _startState, self.RULE_columnExpr) - self.state = 814 + self.state = 834 if not self.precpred(self._ctx, 18): from antlr4.error.Errors import FailedPredicateException raise FailedPredicateException(self, "self.precpred(self._ctx, 18)") - self.state = 818 + self.state = 838 self._errHandler.sync(self) token = self._input.LA(1) if token in [108]: - self.state = 815 + self.state = 835 localctx.operator = self.match(HogQLParser.ASTERISK) pass elif token in [146]: - self.state = 816 + self.state = 836 localctx.operator = self.match(HogQLParser.SLASH) pass elif token in [133]: - self.state = 817 + self.state = 837 localctx.operator = self.match(HogQLParser.PERCENT) pass else: raise NoViableAltException(self) - self.state = 820 + self.state = 840 localctx.right = self.columnExpr(19) pass @@ -6385,29 +6463,29 @@ def columnExpr(self, _p:int=0): localctx = HogQLParser.ColumnExprPrecedence2Context(self, HogQLParser.ColumnExprContext(self, _parentctx, _parentState)) localctx.left = _prevctx self.pushNewRecursionContext(localctx, _startState, self.RULE_columnExpr) - self.state = 821 + self.state = 841 if not self.precpred(self._ctx, 17): from antlr4.error.Errors import FailedPredicateException raise FailedPredicateException(self, "self.precpred(self._ctx, 17)") - self.state = 825 + self.state = 845 self._errHandler.sync(self) token = self._input.LA(1) if token in [134]: - self.state = 822 + self.state = 842 localctx.operator = self.match(HogQLParser.PLUS) pass elif token in [114]: - self.state = 823 + self.state = 843 localctx.operator = self.match(HogQLParser.DASH) pass elif token in [113]: - self.state = 824 + self.state = 844 localctx.operator = self.match(HogQLParser.CONCAT) pass else: raise NoViableAltException(self) - self.state = 827 + self.state = 847 localctx.right = self.columnExpr(18) pass @@ -6415,79 +6493,79 @@ def columnExpr(self, _p:int=0): localctx = HogQLParser.ColumnExprPrecedence3Context(self, HogQLParser.ColumnExprContext(self, _parentctx, _parentState)) localctx.left = _prevctx self.pushNewRecursionContext(localctx, _startState, self.RULE_columnExpr) - self.state = 828 + self.state = 848 if not self.precpred(self._ctx, 16): from antlr4.error.Errors import FailedPredicateException raise FailedPredicateException(self, "self.precpred(self._ctx, 16)") - self.state = 853 + self.state = 873 self._errHandler.sync(self) - la_ = self._interp.adaptivePredict(self._input,96,self._ctx) + la_ = self._interp.adaptivePredict(self._input,102,self._ctx) if la_ == 1: - self.state = 829 + self.state = 849 localctx.operator = self.match(HogQLParser.EQ_DOUBLE) pass elif la_ == 2: - self.state = 830 + self.state = 850 localctx.operator = self.match(HogQLParser.EQ_SINGLE) pass elif la_ == 3: - self.state = 831 + self.state = 851 localctx.operator = self.match(HogQLParser.NOT_EQ) pass elif la_ == 4: - self.state = 832 + self.state = 852 localctx.operator = self.match(HogQLParser.LT_EQ) pass elif la_ == 5: - self.state = 833 + self.state = 853 localctx.operator = self.match(HogQLParser.LT) pass elif la_ == 6: - self.state = 834 + self.state = 854 localctx.operator = self.match(HogQLParser.GT_EQ) pass elif la_ == 7: - self.state = 835 + self.state = 855 localctx.operator = self.match(HogQLParser.GT) pass elif la_ == 8: - self.state = 837 + self.state = 857 self._errHandler.sync(self) _la = self._input.LA(1) if _la==56: - self.state = 836 + self.state = 856 localctx.operator = self.match(HogQLParser.NOT) - self.state = 839 + self.state = 859 self.match(HogQLParser.IN) - self.state = 841 + self.state = 861 self._errHandler.sync(self) - la_ = self._interp.adaptivePredict(self._input,94,self._ctx) + la_ = self._interp.adaptivePredict(self._input,100,self._ctx) if la_ == 1: - self.state = 840 + self.state = 860 self.match(HogQLParser.COHORT) pass elif la_ == 9: - self.state = 844 + self.state = 864 self._errHandler.sync(self) _la = self._input.LA(1) if _la==56: - self.state = 843 + self.state = 863 localctx.operator = self.match(HogQLParser.NOT) - self.state = 846 + self.state = 866 _la = self._input.LA(1) if not(_la==39 or _la==51): self._errHandler.recoverInline(self) @@ -6497,209 +6575,209 @@ def columnExpr(self, _p:int=0): pass elif la_ == 10: - self.state = 847 + self.state = 867 localctx.operator = self.match(HogQLParser.REGEX_SINGLE) pass elif la_ == 11: - self.state = 848 + self.state = 868 localctx.operator = self.match(HogQLParser.REGEX_DOUBLE) pass elif la_ == 12: - self.state = 849 + self.state = 869 localctx.operator = self.match(HogQLParser.NOT_REGEX) pass elif la_ == 13: - self.state = 850 + self.state = 870 localctx.operator = self.match(HogQLParser.IREGEX_SINGLE) pass elif la_ == 14: - self.state = 851 + self.state = 871 localctx.operator = self.match(HogQLParser.IREGEX_DOUBLE) pass elif la_ == 15: - self.state = 852 + self.state = 872 localctx.operator = self.match(HogQLParser.NOT_IREGEX) pass - self.state = 855 + self.state = 875 localctx.right = self.columnExpr(17) pass elif la_ == 4: localctx = HogQLParser.ColumnExprNullishContext(self, HogQLParser.ColumnExprContext(self, _parentctx, _parentState)) self.pushNewRecursionContext(localctx, _startState, self.RULE_columnExpr) - self.state = 856 + self.state = 876 if not self.precpred(self._ctx, 14): from antlr4.error.Errors import FailedPredicateException raise FailedPredicateException(self, "self.precpred(self._ctx, 14)") - self.state = 857 + self.state = 877 self.match(HogQLParser.NULLISH) - self.state = 858 + self.state = 878 self.columnExpr(15) pass elif la_ == 5: localctx = HogQLParser.ColumnExprAndContext(self, HogQLParser.ColumnExprContext(self, _parentctx, _parentState)) self.pushNewRecursionContext(localctx, _startState, self.RULE_columnExpr) - self.state = 859 + self.state = 879 if not self.precpred(self._ctx, 12): from antlr4.error.Errors import FailedPredicateException raise FailedPredicateException(self, "self.precpred(self._ctx, 12)") - self.state = 860 + self.state = 880 self.match(HogQLParser.AND) - self.state = 861 + self.state = 881 self.columnExpr(13) pass elif la_ == 6: localctx = HogQLParser.ColumnExprOrContext(self, HogQLParser.ColumnExprContext(self, _parentctx, _parentState)) self.pushNewRecursionContext(localctx, _startState, self.RULE_columnExpr) - self.state = 862 + self.state = 882 if not self.precpred(self._ctx, 11): from antlr4.error.Errors import FailedPredicateException raise FailedPredicateException(self, "self.precpred(self._ctx, 11)") - self.state = 863 + self.state = 883 self.match(HogQLParser.OR) - self.state = 864 + self.state = 884 self.columnExpr(12) pass elif la_ == 7: localctx = HogQLParser.ColumnExprBetweenContext(self, HogQLParser.ColumnExprContext(self, _parentctx, _parentState)) self.pushNewRecursionContext(localctx, _startState, self.RULE_columnExpr) - self.state = 865 + self.state = 885 if not self.precpred(self._ctx, 10): from antlr4.error.Errors import FailedPredicateException raise FailedPredicateException(self, "self.precpred(self._ctx, 10)") - self.state = 867 + self.state = 887 self._errHandler.sync(self) _la = self._input.LA(1) if _la==56: - self.state = 866 + self.state = 886 self.match(HogQLParser.NOT) - self.state = 869 + self.state = 889 self.match(HogQLParser.BETWEEN) - self.state = 870 + self.state = 890 self.columnExpr(0) - self.state = 871 + self.state = 891 self.match(HogQLParser.AND) - self.state = 872 + self.state = 892 self.columnExpr(11) pass elif la_ == 8: localctx = HogQLParser.ColumnExprTernaryOpContext(self, HogQLParser.ColumnExprContext(self, _parentctx, _parentState)) self.pushNewRecursionContext(localctx, _startState, self.RULE_columnExpr) - self.state = 874 + self.state = 894 if not self.precpred(self._ctx, 9): from antlr4.error.Errors import FailedPredicateException raise FailedPredicateException(self, "self.precpred(self._ctx, 9)") - self.state = 875 + self.state = 895 self.match(HogQLParser.QUERY) - self.state = 876 + self.state = 896 self.columnExpr(0) - self.state = 877 + self.state = 897 self.match(HogQLParser.COLON) - self.state = 878 + self.state = 898 self.columnExpr(9) pass elif la_ == 9: localctx = HogQLParser.ColumnExprArrayAccessContext(self, HogQLParser.ColumnExprContext(self, _parentctx, _parentState)) self.pushNewRecursionContext(localctx, _startState, self.RULE_columnExpr) - self.state = 880 + self.state = 900 if not self.precpred(self._ctx, 22): from antlr4.error.Errors import FailedPredicateException raise FailedPredicateException(self, "self.precpred(self._ctx, 22)") - self.state = 881 + self.state = 901 self.match(HogQLParser.LBRACKET) - self.state = 882 + self.state = 902 self.columnExpr(0) - self.state = 883 + self.state = 903 self.match(HogQLParser.RBRACKET) pass elif la_ == 10: localctx = HogQLParser.ColumnExprTupleAccessContext(self, HogQLParser.ColumnExprContext(self, _parentctx, _parentState)) self.pushNewRecursionContext(localctx, _startState, self.RULE_columnExpr) - self.state = 885 + self.state = 905 if not self.precpred(self._ctx, 21): from antlr4.error.Errors import FailedPredicateException raise FailedPredicateException(self, "self.precpred(self._ctx, 21)") - self.state = 886 + self.state = 906 self.match(HogQLParser.DOT) - self.state = 887 + self.state = 907 self.match(HogQLParser.DECIMAL_LITERAL) pass elif la_ == 11: localctx = HogQLParser.ColumnExprPropertyAccessContext(self, HogQLParser.ColumnExprContext(self, _parentctx, _parentState)) self.pushNewRecursionContext(localctx, _startState, self.RULE_columnExpr) - self.state = 888 + self.state = 908 if not self.precpred(self._ctx, 20): from antlr4.error.Errors import FailedPredicateException raise FailedPredicateException(self, "self.precpred(self._ctx, 20)") - self.state = 889 + self.state = 909 self.match(HogQLParser.DOT) - self.state = 890 + self.state = 910 self.identifier() pass elif la_ == 12: localctx = HogQLParser.ColumnExprIsNullContext(self, HogQLParser.ColumnExprContext(self, _parentctx, _parentState)) self.pushNewRecursionContext(localctx, _startState, self.RULE_columnExpr) - self.state = 891 + self.state = 911 if not self.precpred(self._ctx, 15): from antlr4.error.Errors import FailedPredicateException raise FailedPredicateException(self, "self.precpred(self._ctx, 15)") - self.state = 892 + self.state = 912 self.match(HogQLParser.IS) - self.state = 894 + self.state = 914 self._errHandler.sync(self) _la = self._input.LA(1) if _la==56: - self.state = 893 + self.state = 913 self.match(HogQLParser.NOT) - self.state = 896 + self.state = 916 self.match(HogQLParser.NULL_SQL) pass elif la_ == 13: localctx = HogQLParser.ColumnExprAliasContext(self, HogQLParser.ColumnExprContext(self, _parentctx, _parentState)) self.pushNewRecursionContext(localctx, _startState, self.RULE_columnExpr) - self.state = 897 + self.state = 917 if not self.precpred(self._ctx, 8): from antlr4.error.Errors import FailedPredicateException raise FailedPredicateException(self, "self.precpred(self._ctx, 8)") - self.state = 903 + self.state = 923 self._errHandler.sync(self) - la_ = self._interp.adaptivePredict(self._input,99,self._ctx) + la_ = self._interp.adaptivePredict(self._input,105,self._ctx) if la_ == 1: - self.state = 898 + self.state = 918 self.alias() pass elif la_ == 2: - self.state = 899 + self.state = 919 self.match(HogQLParser.AS) - self.state = 900 + self.state = 920 self.identifier() pass elif la_ == 3: - self.state = 901 + self.state = 921 self.match(HogQLParser.AS) - self.state = 902 + self.state = 922 self.match(HogQLParser.STRING_LITERAL) pass @@ -6707,9 +6785,9 @@ def columnExpr(self, _p:int=0): pass - self.state = 909 + self.state = 929 self._errHandler.sync(self) - _alt = self._interp.adaptivePredict(self._input,101,self._ctx) + _alt = self._interp.adaptivePredict(self._input,107,self._ctx) except RecognitionException as re: localctx.exception = re @@ -6759,17 +6837,17 @@ def columnArgList(self): self._la = 0 # Token type try: self.enterOuterAlt(localctx, 1) - self.state = 910 + self.state = 930 self.columnArgExpr() - self.state = 915 + self.state = 935 self._errHandler.sync(self) _la = self._input.LA(1) while _la==112: - self.state = 911 + self.state = 931 self.match(HogQLParser.COMMA) - self.state = 912 + self.state = 932 self.columnArgExpr() - self.state = 917 + self.state = 937 self._errHandler.sync(self) _la = self._input.LA(1) @@ -6814,18 +6892,18 @@ def columnArgExpr(self): localctx = HogQLParser.ColumnArgExprContext(self, self._ctx, self.state) self.enterRule(localctx, 110, self.RULE_columnArgExpr) try: - self.state = 920 + self.state = 940 self._errHandler.sync(self) - la_ = self._interp.adaptivePredict(self._input,103,self._ctx) + la_ = self._interp.adaptivePredict(self._input,109,self._ctx) if la_ == 1: self.enterOuterAlt(localctx, 1) - self.state = 918 + self.state = 938 self.columnLambdaExpr() pass elif la_ == 2: self.enterOuterAlt(localctx, 2) - self.state = 919 + self.state = 939 self.columnExpr(0) pass @@ -6891,41 +6969,41 @@ def columnLambdaExpr(self): self._la = 0 # Token type try: self.enterOuterAlt(localctx, 1) - self.state = 941 + self.state = 961 self._errHandler.sync(self) token = self._input.LA(1) if token in [126]: - self.state = 922 + self.state = 942 self.match(HogQLParser.LPAREN) - self.state = 923 + self.state = 943 self.identifier() - self.state = 928 + self.state = 948 self._errHandler.sync(self) _la = self._input.LA(1) while _la==112: - self.state = 924 + self.state = 944 self.match(HogQLParser.COMMA) - self.state = 925 + self.state = 945 self.identifier() - self.state = 930 + self.state = 950 self._errHandler.sync(self) _la = self._input.LA(1) - self.state = 931 + self.state = 951 self.match(HogQLParser.RPAREN) pass elif token in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 42, 43, 44, 45, 46, 47, 48, 49, 51, 52, 53, 54, 56, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 97, 98, 99, 101]: - self.state = 933 + self.state = 953 self.identifier() - self.state = 938 + self.state = 958 self._errHandler.sync(self) _la = self._input.LA(1) while _la==112: - self.state = 934 + self.state = 954 self.match(HogQLParser.COMMA) - self.state = 935 + self.state = 955 self.identifier() - self.state = 940 + self.state = 960 self._errHandler.sync(self) _la = self._input.LA(1) @@ -6933,9 +7011,9 @@ def columnLambdaExpr(self): else: raise NoViableAltException(self) - self.state = 943 + self.state = 963 self.match(HogQLParser.ARROW) - self.state = 944 + self.state = 964 self.columnExpr(0) except RecognitionException as re: localctx.exception = re @@ -7040,66 +7118,66 @@ def hogqlxTagElement(self): self.enterRule(localctx, 114, self.RULE_hogqlxTagElement) self._la = 0 # Token type try: - self.state = 974 + self.state = 994 self._errHandler.sync(self) - la_ = self._interp.adaptivePredict(self._input,110,self._ctx) + la_ = self._interp.adaptivePredict(self._input,116,self._ctx) if la_ == 1: localctx = HogQLParser.HogqlxTagElementClosedContext(self, localctx) self.enterOuterAlt(localctx, 1) - self.state = 946 + self.state = 966 self.match(HogQLParser.LT) - self.state = 947 + self.state = 967 self.identifier() - self.state = 951 + self.state = 971 self._errHandler.sync(self) _la = self._input.LA(1) while (((_la) & ~0x3f) == 0 and ((1 << _la) & -181272084561788930) != 0) or ((((_la - 64)) & ~0x3f) == 0 and ((1 << (_la - 64)) & 201863462911) != 0): - self.state = 948 + self.state = 968 self.hogqlxTagAttribute() - self.state = 953 + self.state = 973 self._errHandler.sync(self) _la = self._input.LA(1) - self.state = 954 + self.state = 974 self.match(HogQLParser.SLASH) - self.state = 955 + self.state = 975 self.match(HogQLParser.GT) pass elif la_ == 2: localctx = HogQLParser.HogqlxTagElementNestedContext(self, localctx) self.enterOuterAlt(localctx, 2) - self.state = 957 + self.state = 977 self.match(HogQLParser.LT) - self.state = 958 + self.state = 978 self.identifier() - self.state = 962 + self.state = 982 self._errHandler.sync(self) _la = self._input.LA(1) while (((_la) & ~0x3f) == 0 and ((1 << _la) & -181272084561788930) != 0) or ((((_la - 64)) & ~0x3f) == 0 and ((1 << (_la - 64)) & 201863462911) != 0): - self.state = 959 + self.state = 979 self.hogqlxTagAttribute() - self.state = 964 + self.state = 984 self._errHandler.sync(self) _la = self._input.LA(1) - self.state = 965 + self.state = 985 self.match(HogQLParser.GT) - self.state = 967 + self.state = 987 self._errHandler.sync(self) - la_ = self._interp.adaptivePredict(self._input,109,self._ctx) + la_ = self._interp.adaptivePredict(self._input,115,self._ctx) if la_ == 1: - self.state = 966 + self.state = 986 self.hogqlxTagElement() - self.state = 969 + self.state = 989 self.match(HogQLParser.LT) - self.state = 970 + self.state = 990 self.match(HogQLParser.SLASH) - self.state = 971 + self.state = 991 self.identifier() - self.state = 972 + self.state = 992 self.match(HogQLParser.GT) pass @@ -7158,36 +7236,36 @@ def hogqlxTagAttribute(self): localctx = HogQLParser.HogqlxTagAttributeContext(self, self._ctx, self.state) self.enterRule(localctx, 116, self.RULE_hogqlxTagAttribute) try: - self.state = 987 + self.state = 1007 self._errHandler.sync(self) - la_ = self._interp.adaptivePredict(self._input,111,self._ctx) + la_ = self._interp.adaptivePredict(self._input,117,self._ctx) if la_ == 1: self.enterOuterAlt(localctx, 1) - self.state = 976 + self.state = 996 self.identifier() - self.state = 977 + self.state = 997 self.match(HogQLParser.EQ_SINGLE) - self.state = 978 + self.state = 998 self.string() pass elif la_ == 2: self.enterOuterAlt(localctx, 2) - self.state = 980 + self.state = 1000 self.identifier() - self.state = 981 + self.state = 1001 self.match(HogQLParser.EQ_SINGLE) - self.state = 982 + self.state = 1002 self.match(HogQLParser.LBRACE) - self.state = 983 + self.state = 1003 self.columnExpr(0) - self.state = 984 + self.state = 1004 self.match(HogQLParser.RBRACE) pass elif la_ == 3: self.enterOuterAlt(localctx, 3) - self.state = 986 + self.state = 1006 self.identifier() pass @@ -7240,17 +7318,17 @@ def withExprList(self): self._la = 0 # Token type try: self.enterOuterAlt(localctx, 1) - self.state = 989 + self.state = 1009 self.withExpr() - self.state = 994 + self.state = 1014 self._errHandler.sync(self) _la = self._input.LA(1) while _la==112: - self.state = 990 + self.state = 1010 self.match(HogQLParser.COMMA) - self.state = 991 + self.state = 1011 self.withExpr() - self.state = 996 + self.state = 1016 self._errHandler.sync(self) _la = self._input.LA(1) @@ -7334,32 +7412,32 @@ def withExpr(self): localctx = HogQLParser.WithExprContext(self, self._ctx, self.state) self.enterRule(localctx, 120, self.RULE_withExpr) try: - self.state = 1007 + self.state = 1027 self._errHandler.sync(self) - la_ = self._interp.adaptivePredict(self._input,113,self._ctx) + la_ = self._interp.adaptivePredict(self._input,119,self._ctx) if la_ == 1: localctx = HogQLParser.WithExprSubqueryContext(self, localctx) self.enterOuterAlt(localctx, 1) - self.state = 997 + self.state = 1017 self.identifier() - self.state = 998 + self.state = 1018 self.match(HogQLParser.AS) - self.state = 999 + self.state = 1019 self.match(HogQLParser.LPAREN) - self.state = 1000 + self.state = 1020 self.selectUnionStmt() - self.state = 1001 + self.state = 1021 self.match(HogQLParser.RPAREN) pass elif la_ == 2: localctx = HogQLParser.WithExprColumnContext(self, localctx) self.enterOuterAlt(localctx, 2) - self.state = 1003 + self.state = 1023 self.columnExpr(0) - self.state = 1004 + self.state = 1024 self.match(HogQLParser.AS) - self.state = 1005 + self.state = 1025 self.identifier() pass @@ -7412,27 +7490,27 @@ def columnIdentifier(self): localctx = HogQLParser.ColumnIdentifierContext(self, self._ctx, self.state) self.enterRule(localctx, 122, self.RULE_columnIdentifier) try: - self.state = 1016 + self.state = 1036 self._errHandler.sync(self) token = self._input.LA(1) if token in [124]: self.enterOuterAlt(localctx, 1) - self.state = 1009 + self.state = 1029 self.placeholder() pass elif token in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 42, 43, 44, 45, 46, 47, 48, 49, 51, 52, 53, 54, 56, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 97, 98, 99, 101]: self.enterOuterAlt(localctx, 2) - self.state = 1013 + self.state = 1033 self._errHandler.sync(self) - la_ = self._interp.adaptivePredict(self._input,114,self._ctx) + la_ = self._interp.adaptivePredict(self._input,120,self._ctx) if la_ == 1: - self.state = 1010 + self.state = 1030 self.tableIdentifier() - self.state = 1011 + self.state = 1031 self.match(HogQLParser.DOT) - self.state = 1015 + self.state = 1035 self.nestedIdentifier() pass else: @@ -7485,20 +7563,20 @@ def nestedIdentifier(self): self.enterRule(localctx, 124, self.RULE_nestedIdentifier) try: self.enterOuterAlt(localctx, 1) - self.state = 1018 + self.state = 1038 self.identifier() - self.state = 1023 + self.state = 1043 self._errHandler.sync(self) - _alt = self._interp.adaptivePredict(self._input,116,self._ctx) + _alt = self._interp.adaptivePredict(self._input,122,self._ctx) while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER: if _alt==1: - self.state = 1019 + self.state = 1039 self.match(HogQLParser.DOT) - self.state = 1020 + self.state = 1040 self.identifier() - self.state = 1025 + self.state = 1045 self._errHandler.sync(self) - _alt = self._interp.adaptivePredict(self._input,116,self._ctx) + _alt = self._interp.adaptivePredict(self._input,122,self._ctx) except RecognitionException as re: localctx.exception = re @@ -7649,15 +7727,15 @@ def tableExpr(self, _p:int=0): self.enterRecursionRule(localctx, 126, self.RULE_tableExpr, _p) try: self.enterOuterAlt(localctx, 1) - self.state = 1035 + self.state = 1055 self._errHandler.sync(self) - la_ = self._interp.adaptivePredict(self._input,117,self._ctx) + la_ = self._interp.adaptivePredict(self._input,123,self._ctx) if la_ == 1: localctx = HogQLParser.TableExprIdentifierContext(self, localctx) self._ctx = localctx _prevctx = localctx - self.state = 1027 + self.state = 1047 self.tableIdentifier() pass @@ -7665,7 +7743,7 @@ def tableExpr(self, _p:int=0): localctx = HogQLParser.TableExprFunctionContext(self, localctx) self._ctx = localctx _prevctx = localctx - self.state = 1028 + self.state = 1048 self.tableFunctionExpr() pass @@ -7673,11 +7751,11 @@ def tableExpr(self, _p:int=0): localctx = HogQLParser.TableExprSubqueryContext(self, localctx) self._ctx = localctx _prevctx = localctx - self.state = 1029 + self.state = 1049 self.match(HogQLParser.LPAREN) - self.state = 1030 + self.state = 1050 self.selectUnionStmt() - self.state = 1031 + self.state = 1051 self.match(HogQLParser.RPAREN) pass @@ -7685,7 +7763,7 @@ def tableExpr(self, _p:int=0): localctx = HogQLParser.TableExprTagContext(self, localctx) self._ctx = localctx _prevctx = localctx - self.state = 1033 + self.state = 1053 self.hogqlxTagElement() pass @@ -7693,15 +7771,15 @@ def tableExpr(self, _p:int=0): localctx = HogQLParser.TableExprPlaceholderContext(self, localctx) self._ctx = localctx _prevctx = localctx - self.state = 1034 + self.state = 1054 self.placeholder() pass self._ctx.stop = self._input.LT(-1) - self.state = 1045 + self.state = 1065 self._errHandler.sync(self) - _alt = self._interp.adaptivePredict(self._input,119,self._ctx) + _alt = self._interp.adaptivePredict(self._input,125,self._ctx) while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER: if _alt==1: if self._parseListeners is not None: @@ -7709,29 +7787,29 @@ def tableExpr(self, _p:int=0): _prevctx = localctx localctx = HogQLParser.TableExprAliasContext(self, HogQLParser.TableExprContext(self, _parentctx, _parentState)) self.pushNewRecursionContext(localctx, _startState, self.RULE_tableExpr) - self.state = 1037 + self.state = 1057 if not self.precpred(self._ctx, 3): from antlr4.error.Errors import FailedPredicateException raise FailedPredicateException(self, "self.precpred(self._ctx, 3)") - self.state = 1041 + self.state = 1061 self._errHandler.sync(self) token = self._input.LA(1) if token in [19, 28, 37, 46, 101]: - self.state = 1038 + self.state = 1058 self.alias() pass elif token in [6]: - self.state = 1039 + self.state = 1059 self.match(HogQLParser.AS) - self.state = 1040 + self.state = 1060 self.identifier() pass else: raise NoViableAltException(self) - self.state = 1047 + self.state = 1067 self._errHandler.sync(self) - _alt = self._interp.adaptivePredict(self._input,119,self._ctx) + _alt = self._interp.adaptivePredict(self._input,125,self._ctx) except RecognitionException as re: localctx.exception = re @@ -7782,19 +7860,19 @@ def tableFunctionExpr(self): self._la = 0 # Token type try: self.enterOuterAlt(localctx, 1) - self.state = 1048 + self.state = 1068 self.identifier() - self.state = 1049 + self.state = 1069 self.match(HogQLParser.LPAREN) - self.state = 1051 + self.state = 1071 self._errHandler.sync(self) _la = self._input.LA(1) if (((_la) & ~0x3f) == 0 and ((1 << _la) & -1125900443713538) != 0) or ((((_la - 64)) & ~0x3f) == 0 and ((1 << (_la - 64)) & 8076106347046764543) != 0) or ((((_la - 128)) & ~0x3f) == 0 and ((1 << (_la - 128)) & 577) != 0): - self.state = 1050 + self.state = 1070 self.tableArgList() - self.state = 1053 + self.state = 1073 self.match(HogQLParser.RPAREN) except RecognitionException as re: localctx.exception = re @@ -7841,17 +7919,17 @@ def tableIdentifier(self): self.enterRule(localctx, 130, self.RULE_tableIdentifier) try: self.enterOuterAlt(localctx, 1) - self.state = 1058 + self.state = 1078 self._errHandler.sync(self) - la_ = self._interp.adaptivePredict(self._input,121,self._ctx) + la_ = self._interp.adaptivePredict(self._input,127,self._ctx) if la_ == 1: - self.state = 1055 + self.state = 1075 self.databaseIdentifier() - self.state = 1056 + self.state = 1076 self.match(HogQLParser.DOT) - self.state = 1060 + self.state = 1080 self.identifier() except RecognitionException as re: localctx.exception = re @@ -7901,17 +7979,17 @@ def tableArgList(self): self._la = 0 # Token type try: self.enterOuterAlt(localctx, 1) - self.state = 1062 + self.state = 1082 self.columnExpr(0) - self.state = 1067 + self.state = 1087 self._errHandler.sync(self) _la = self._input.LA(1) while _la==112: - self.state = 1063 + self.state = 1083 self.match(HogQLParser.COMMA) - self.state = 1064 + self.state = 1084 self.columnExpr(0) - self.state = 1069 + self.state = 1089 self._errHandler.sync(self) _la = self._input.LA(1) @@ -7953,7 +8031,7 @@ def databaseIdentifier(self): self.enterRule(localctx, 134, self.RULE_databaseIdentifier) try: self.enterOuterAlt(localctx, 1) - self.state = 1070 + self.state = 1090 self.identifier() except RecognitionException as re: localctx.exception = re @@ -8004,19 +8082,19 @@ def floatingLiteral(self): self.enterRule(localctx, 136, self.RULE_floatingLiteral) self._la = 0 # Token type try: - self.state = 1080 + self.state = 1100 self._errHandler.sync(self) token = self._input.LA(1) if token in [102]: self.enterOuterAlt(localctx, 1) - self.state = 1072 + self.state = 1092 self.match(HogQLParser.FLOATING_LITERAL) pass elif token in [116]: self.enterOuterAlt(localctx, 2) - self.state = 1073 + self.state = 1093 self.match(HogQLParser.DOT) - self.state = 1074 + self.state = 1094 _la = self._input.LA(1) if not(_la==103 or _la==104): self._errHandler.recoverInline(self) @@ -8026,15 +8104,15 @@ def floatingLiteral(self): pass elif token in [104]: self.enterOuterAlt(localctx, 3) - self.state = 1075 + self.state = 1095 self.match(HogQLParser.DECIMAL_LITERAL) - self.state = 1076 + self.state = 1096 self.match(HogQLParser.DOT) - self.state = 1078 + self.state = 1098 self._errHandler.sync(self) - la_ = self._interp.adaptivePredict(self._input,123,self._ctx) + la_ = self._interp.adaptivePredict(self._input,129,self._ctx) if la_ == 1: - self.state = 1077 + self.state = 1097 _la = self._input.LA(1) if not(_la==103 or _la==104): self._errHandler.recoverInline(self) @@ -8107,11 +8185,11 @@ def numberLiteral(self): self._la = 0 # Token type try: self.enterOuterAlt(localctx, 1) - self.state = 1083 + self.state = 1103 self._errHandler.sync(self) _la = self._input.LA(1) if _la==114 or _la==134: - self.state = 1082 + self.state = 1102 _la = self._input.LA(1) if not(_la==114 or _la==134): self._errHandler.recoverInline(self) @@ -8120,36 +8198,36 @@ def numberLiteral(self): self.consume() - self.state = 1091 + self.state = 1111 self._errHandler.sync(self) - la_ = self._interp.adaptivePredict(self._input,126,self._ctx) + la_ = self._interp.adaptivePredict(self._input,132,self._ctx) if la_ == 1: - self.state = 1085 + self.state = 1105 self.floatingLiteral() pass elif la_ == 2: - self.state = 1086 + self.state = 1106 self.match(HogQLParser.OCTAL_LITERAL) pass elif la_ == 3: - self.state = 1087 + self.state = 1107 self.match(HogQLParser.DECIMAL_LITERAL) pass elif la_ == 4: - self.state = 1088 + self.state = 1108 self.match(HogQLParser.HEXADECIMAL_LITERAL) pass elif la_ == 5: - self.state = 1089 + self.state = 1109 self.match(HogQLParser.INF) pass elif la_ == 6: - self.state = 1090 + self.state = 1110 self.match(HogQLParser.NAN_SQL) pass @@ -8197,22 +8275,22 @@ def literal(self): localctx = HogQLParser.LiteralContext(self, self._ctx, self.state) self.enterRule(localctx, 140, self.RULE_literal) try: - self.state = 1096 + self.state = 1116 self._errHandler.sync(self) token = self._input.LA(1) if token in [41, 55, 102, 103, 104, 105, 114, 116, 134]: self.enterOuterAlt(localctx, 1) - self.state = 1093 + self.state = 1113 self.numberLiteral() pass elif token in [106]: self.enterOuterAlt(localctx, 2) - self.state = 1094 + self.state = 1114 self.match(HogQLParser.STRING_LITERAL) pass elif token in [57]: self.enterOuterAlt(localctx, 3) - self.state = 1095 + self.state = 1115 self.match(HogQLParser.NULL_SQL) pass else: @@ -8277,7 +8355,7 @@ def interval(self): self._la = 0 # Token type try: self.enterOuterAlt(localctx, 1) - self.state = 1098 + self.state = 1118 _la = self._input.LA(1) if not((((_la) & ~0x3f) == 0 and ((1 << _la) & 27021666484748288) != 0) or ((((_la - 68)) & ~0x3f) == 0 and ((1 << (_la - 68)) & 2181038337) != 0)): self._errHandler.recoverInline(self) @@ -8574,7 +8652,7 @@ def keyword(self): self._la = 0 # Token type try: self.enterOuterAlt(localctx, 1) - self.state = 1100 + self.state = 1120 _la = self._input.LA(1) if not((((_la) & ~0x3f) == 0 and ((1 << _la) & -208293751046537218) != 0) or ((((_la - 64)) & ~0x3f) == 0 and ((1 << (_la - 64)) & 29527896047) != 0)): self._errHandler.recoverInline(self) @@ -8628,7 +8706,7 @@ def keywordForAlias(self): self._la = 0 # Token type try: self.enterOuterAlt(localctx, 1) - self.state = 1102 + self.state = 1122 _la = self._input.LA(1) if not((((_la) & ~0x3f) == 0 and ((1 << _la) & 70506452090880) != 0)): self._errHandler.recoverInline(self) @@ -8675,17 +8753,17 @@ def alias(self): localctx = HogQLParser.AliasContext(self, self._ctx, self.state) self.enterRule(localctx, 148, self.RULE_alias) try: - self.state = 1106 + self.state = 1126 self._errHandler.sync(self) token = self._input.LA(1) if token in [101]: self.enterOuterAlt(localctx, 1) - self.state = 1104 + self.state = 1124 self.match(HogQLParser.IDENTIFIER) pass elif token in [19, 28, 37, 46]: self.enterOuterAlt(localctx, 2) - self.state = 1105 + self.state = 1125 self.keywordForAlias() pass else: @@ -8735,22 +8813,22 @@ def identifier(self): localctx = HogQLParser.IdentifierContext(self, self._ctx, self.state) self.enterRule(localctx, 150, self.RULE_identifier) try: - self.state = 1111 + self.state = 1131 self._errHandler.sync(self) token = self._input.LA(1) if token in [101]: self.enterOuterAlt(localctx, 1) - self.state = 1108 + self.state = 1128 self.match(HogQLParser.IDENTIFIER) pass elif token in [20, 36, 53, 54, 68, 76, 93, 99]: self.enterOuterAlt(localctx, 2) - self.state = 1109 + self.state = 1129 self.interval() pass elif token in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 21, 22, 23, 24, 25, 26, 27, 28, 30, 31, 32, 33, 34, 35, 37, 38, 39, 40, 42, 43, 44, 45, 46, 47, 48, 49, 51, 52, 56, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 69, 70, 71, 72, 73, 74, 75, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 94, 95, 97, 98]: self.enterOuterAlt(localctx, 3) - self.state = 1110 + self.state = 1130 self.keyword() pass else: @@ -8801,11 +8879,11 @@ def enumValue(self): self.enterRule(localctx, 152, self.RULE_enumValue) try: self.enterOuterAlt(localctx, 1) - self.state = 1113 + self.state = 1133 self.string() - self.state = 1114 + self.state = 1134 self.match(HogQLParser.EQ_SINGLE) - self.state = 1115 + self.state = 1135 self.numberLiteral() except RecognitionException as re: localctx.exception = re @@ -8851,11 +8929,11 @@ def placeholder(self): self.enterRule(localctx, 154, self.RULE_placeholder) try: self.enterOuterAlt(localctx, 1) - self.state = 1117 + self.state = 1137 self.match(HogQLParser.LBRACE) - self.state = 1118 + self.state = 1138 self.identifier() - self.state = 1119 + self.state = 1139 self.match(HogQLParser.RBRACE) except RecognitionException as re: localctx.exception = re @@ -8897,17 +8975,17 @@ def string(self): localctx = HogQLParser.StringContext(self, self._ctx, self.state) self.enterRule(localctx, 156, self.RULE_string) try: - self.state = 1123 + self.state = 1143 self._errHandler.sync(self) token = self._input.LA(1) if token in [106]: self.enterOuterAlt(localctx, 1) - self.state = 1121 + self.state = 1141 self.match(HogQLParser.STRING_LITERAL) pass elif token in [137]: self.enterOuterAlt(localctx, 2) - self.state = 1122 + self.state = 1142 self.templateString() pass else: @@ -8961,19 +9039,19 @@ def templateString(self): self._la = 0 # Token type try: self.enterOuterAlt(localctx, 1) - self.state = 1125 + self.state = 1145 self.match(HogQLParser.QUOTE_SINGLE_TEMPLATE) - self.state = 1129 + self.state = 1149 self._errHandler.sync(self) _la = self._input.LA(1) while _la==151 or _la==152: - self.state = 1126 + self.state = 1146 self.stringContents() - self.state = 1131 + self.state = 1151 self._errHandler.sync(self) _la = self._input.LA(1) - self.state = 1132 + self.state = 1152 self.match(HogQLParser.QUOTE_SINGLE) except RecognitionException as re: localctx.exception = re @@ -9021,21 +9099,21 @@ def stringContents(self): localctx = HogQLParser.StringContentsContext(self, self._ctx, self.state) self.enterRule(localctx, 160, self.RULE_stringContents) try: - self.state = 1139 + self.state = 1159 self._errHandler.sync(self) token = self._input.LA(1) if token in [152]: self.enterOuterAlt(localctx, 1) - self.state = 1134 + self.state = 1154 self.match(HogQLParser.STRING_ESCAPE_TRIGGER) - self.state = 1135 + self.state = 1155 self.columnExpr(0) - self.state = 1136 + self.state = 1156 self.match(HogQLParser.RBRACE) pass elif token in [151]: self.enterOuterAlt(localctx, 2) - self.state = 1138 + self.state = 1158 self.match(HogQLParser.STRING_TEXT) pass else: @@ -9089,19 +9167,19 @@ def fullTemplateString(self): self._la = 0 # Token type try: self.enterOuterAlt(localctx, 1) - self.state = 1141 + self.state = 1161 self.match(HogQLParser.QUOTE_SINGLE_TEMPLATE_FULL) - self.state = 1145 + self.state = 1165 self._errHandler.sync(self) _la = self._input.LA(1) while _la==153 or _la==154: - self.state = 1142 + self.state = 1162 self.stringContentsFull() - self.state = 1147 + self.state = 1167 self._errHandler.sync(self) _la = self._input.LA(1) - self.state = 1148 + self.state = 1168 self.match(HogQLParser.EOF) except RecognitionException as re: localctx.exception = re @@ -9149,21 +9227,21 @@ def stringContentsFull(self): localctx = HogQLParser.StringContentsFullContext(self, self._ctx, self.state) self.enterRule(localctx, 164, self.RULE_stringContentsFull) try: - self.state = 1155 + self.state = 1175 self._errHandler.sync(self) token = self._input.LA(1) if token in [154]: self.enterOuterAlt(localctx, 1) - self.state = 1150 + self.state = 1170 self.match(HogQLParser.FULL_STRING_ESCAPE_TRIGGER) - self.state = 1151 + self.state = 1171 self.columnExpr(0) - self.state = 1152 + self.state = 1172 self.match(HogQLParser.RBRACE) pass elif token in [153]: self.enterOuterAlt(localctx, 2) - self.state = 1154 + self.state = 1174 self.match(HogQLParser.FULL_STRING_TEXT) pass else: diff --git a/posthog/hogql/modifiers.py b/posthog/hogql/modifiers.py index ce17684f47f3d..3aedc9572a4b8 100644 --- a/posthog/hogql/modifiers.py +++ b/posthog/hogql/modifiers.py @@ -34,26 +34,26 @@ def create_default_modifiers_for_team( def set_default_modifier_values(modifiers: HogQLQueryModifiers, team: "Team"): if modifiers.personsOnEventsMode is None: - modifiers.personsOnEventsMode = team.person_on_events_mode or PersonsOnEventsMode.disabled + modifiers.personsOnEventsMode = team.person_on_events_mode or PersonsOnEventsMode.DISABLED if modifiers.personsArgMaxVersion is None: - modifiers.personsArgMaxVersion = PersonsArgMaxVersion.auto + modifiers.personsArgMaxVersion = PersonsArgMaxVersion.AUTO if modifiers.inCohortVia is None: - modifiers.inCohortVia = InCohortVia.auto + modifiers.inCohortVia = InCohortVia.AUTO - if modifiers.materializationMode is None or modifiers.materializationMode == MaterializationMode.auto: - modifiers.materializationMode = MaterializationMode.legacy_null_as_null + if modifiers.materializationMode is None or modifiers.materializationMode == MaterializationMode.AUTO: + modifiers.materializationMode = MaterializationMode.LEGACY_NULL_AS_NULL if modifiers.optimizeJoinedFilters is None: modifiers.optimizeJoinedFilters = False if modifiers.bounceRatePageViewMode is None: - modifiers.bounceRatePageViewMode = BounceRatePageViewMode.count_pageviews + modifiers.bounceRatePageViewMode = BounceRatePageViewMode.COUNT_PAGEVIEWS def set_default_in_cohort_via(modifiers: HogQLQueryModifiers) -> HogQLQueryModifiers: - if modifiers.inCohortVia is None or modifiers.inCohortVia == InCohortVia.auto: - modifiers.inCohortVia = InCohortVia.subquery + if modifiers.inCohortVia is None or modifiers.inCohortVia == InCohortVia.AUTO: + modifiers.inCohortVia = InCohortVia.SUBQUERY return modifiers diff --git a/posthog/hogql/parser.py b/posthog/hogql/parser.py index bab0e9486d003..bf08f5c122635 100644 --- a/posthog/hogql/parser.py +++ b/posthog/hogql/parser.py @@ -822,14 +822,16 @@ def visitColumnExprNot(self, ctx: HogQLParser.ColumnExprNotContext): def visitColumnExprWinFunctionTarget(self, ctx: HogQLParser.ColumnExprWinFunctionTargetContext): return ast.WindowFunction( name=self.visit(ctx.identifier(0)), - args=self.visit(ctx.columnExprList()) if ctx.columnExprList() else [], + exprs=self.visit(ctx.columnExprList()) if ctx.columnExprList() else [], + args=self.visit(ctx.columnArgList()) if ctx.columnArgList() else [], over_identifier=self.visit(ctx.identifier(1)), ) def visitColumnExprWinFunction(self, ctx: HogQLParser.ColumnExprWinFunctionContext): return ast.WindowFunction( name=self.visit(ctx.identifier()), - args=self.visit(ctx.columnExprList()) if ctx.columnExprList() else [], + exprs=self.visit(ctx.columnExprList()) if ctx.columnExprList() else [], + args=self.visit(ctx.columnArgList()) if ctx.columnArgList() else [], over_expr=self.visit(ctx.windowExpr()) if ctx.windowExpr() else None, ) diff --git a/posthog/hogql/printer.py b/posthog/hogql/printer.py index 69b6ae1ef342a..3104d121112de 100644 --- a/posthog/hogql/printer.py +++ b/posthog/hogql/printer.py @@ -100,12 +100,12 @@ def prepare_ast_for_printing( context.modifiers = set_default_in_cohort_via(context.modifiers) - if context.modifiers.inCohortVia == InCohortVia.leftjoin_conjoined: + if context.modifiers.inCohortVia == InCohortVia.LEFTJOIN_CONJOINED: with context.timings.measure("resolve_in_cohorts_conjoined"): resolve_in_cohorts_conjoined(node, dialect, context, stack) with context.timings.measure("resolve_types"): node = resolve_types(node, context, dialect=dialect, scopes=[node.type for node in stack] if stack else None) - if context.modifiers.inCohortVia == InCohortVia.leftjoin: + if context.modifiers.inCohortVia == InCohortVia.LEFTJOIN: with context.timings.measure("resolve_in_cohorts"): resolve_in_cohorts(node, dialect, stack, context) if dialect == "clickhouse": @@ -573,23 +573,23 @@ def visit_compare_operation(self, node: ast.CompareOperation): value_if_one_side_is_null = True elif node.op == ast.CompareOperationOp.Gt: op = f"greater({left}, {right})" - constant_lambda = ( - lambda left_op, right_op: left_op > right_op if left_op is not None and right_op is not None else False + constant_lambda = lambda left_op, right_op: ( + left_op > right_op if left_op is not None and right_op is not None else False ) elif node.op == ast.CompareOperationOp.GtEq: op = f"greaterOrEquals({left}, {right})" - constant_lambda = ( - lambda left_op, right_op: left_op >= right_op if left_op is not None and right_op is not None else False + constant_lambda = lambda left_op, right_op: ( + left_op >= right_op if left_op is not None and right_op is not None else False ) elif node.op == ast.CompareOperationOp.Lt: op = f"less({left}, {right})" - constant_lambda = ( - lambda left_op, right_op: left_op < right_op if left_op is not None and right_op is not None else False + constant_lambda = lambda left_op, right_op: ( + left_op < right_op if left_op is not None and right_op is not None else False ) elif node.op == ast.CompareOperationOp.LtEq: op = f"lessOrEquals({left}, {right})" - constant_lambda = ( - lambda left_op, right_op: left_op <= right_op if left_op is not None and right_op is not None else False + constant_lambda = lambda left_op, right_op: ( + left_op <= right_op if left_op is not None and right_op is not None else False ) else: raise ImpossibleASTError(f"Unknown CompareOperationOp: {node.op.name}") @@ -956,7 +956,7 @@ def visit_field_type(self, type: ast.FieldType): and type.name == "properties" and type.table_type.field == "poe" ): - if self.context.modifiers.personsOnEventsMode != PersonsOnEventsMode.disabled: + if self.context.modifiers.personsOnEventsMode != PersonsOnEventsMode.DISABLED: field_sql = "person_properties" else: field_sql = "person_props" @@ -980,7 +980,7 @@ def visit_field_type(self, type: ast.FieldType): # :KLUDGE: Legacy person properties handling. Only used within non-HogQL queries, such as insights. if self.context.within_non_hogql_query and field_sql == "events__pdi__person.properties": - if self.context.modifiers.personsOnEventsMode != PersonsOnEventsMode.disabled: + if self.context.modifiers.personsOnEventsMode != PersonsOnEventsMode.DISABLED: field_sql = "person_properties" else: field_sql = "person_props" @@ -1030,7 +1030,7 @@ def visit_property_type(self, type: ast.PropertyType): or (isinstance(table, ast.VirtualTableType) and table.field == "poe") ): # :KLUDGE: Legacy person properties handling. Only used within non-HogQL queries, such as insights. - if self.context.modifiers.personsOnEventsMode != PersonsOnEventsMode.disabled: + if self.context.modifiers.personsOnEventsMode != PersonsOnEventsMode.DISABLED: materialized_column = self._get_materialized_column( "events", str(type.chain[0]), "person_properties" ) @@ -1041,9 +1041,9 @@ def visit_property_type(self, type: ast.PropertyType): if materialized_property_sql is not None: # TODO: rematerialize all columns to properly support empty strings and "null" string values. - if self.context.modifiers.materializationMode == MaterializationMode.legacy_null_as_string: + if self.context.modifiers.materializationMode == MaterializationMode.LEGACY_NULL_AS_STRING: materialized_property_sql = f"nullIf({materialized_property_sql}, '')" - else: # MaterializationMode.auto.legacy_null_as_null + else: # MaterializationMode AUTO or LEGACY_NULL_AS_NULL materialized_property_sql = f"nullIf(nullIf({materialized_property_sql}, ''), 'null')" if len(type.chain) == 1: @@ -1140,8 +1140,11 @@ def visit_window_expr(self, node: ast.WindowExpr): return " ".join(strings) def visit_window_function(self, node: ast.WindowFunction): + identifier = self._print_identifier(node.name) + exprs = ", ".join(self.visit(expr) for expr in node.exprs or []) + args = "(" + (", ".join(self.visit(arg) for arg in node.args or [])) + ")" if node.args else "" over = f"({self.visit(node.over_expr)})" if node.over_expr else self._print_identifier(node.over_identifier) - return f"{self._print_identifier(node.name)}({', '.join(self.visit(expr) for expr in node.args or [])}) OVER {over}" + return f"{identifier}({exprs}){args} OVER {over}" def visit_window_frame_expr(self, node: ast.WindowFrameExpr): if node.frame_type == "PRECEDING": diff --git a/posthog/hogql/property.py b/posthog/hogql/property.py index 056334812f8f5..652a46fee9141 100644 --- a/posthog/hogql/property.py +++ b/posthog/hogql/property.py @@ -105,8 +105,8 @@ def property_to_expr( raise NotImplementedError(f'PropertyGroup of unknown type "{property.type}"') if ( (isinstance(property, PropertyGroupFilter) or isinstance(property, PropertyGroupFilterValue)) - and property.type != FilterLogicalOperator.AND - and property.type != FilterLogicalOperator.OR + and property.type != FilterLogicalOperator.AND_ + and property.type != FilterLogicalOperator.OR_ ): raise NotImplementedError(f'PropertyGroupFilter of unknown type "{property.type}"') @@ -115,7 +115,7 @@ def property_to_expr( if len(property.values) == 1: return property_to_expr(property.values[0], team, scope) - if property.type == PropertyOperatorType.AND or property.type == FilterLogicalOperator.AND: + if property.type == PropertyOperatorType.AND or property.type == FilterLogicalOperator.AND_: return ast.And(exprs=[property_to_expr(p, team, scope) for p in property.values]) else: return ast.Or(exprs=[property_to_expr(p, team, scope) for p in property.values]) @@ -143,7 +143,7 @@ def property_to_expr( ): if (scope == "person" and property.type != "person") or (scope == "session" and property.type != "session"): raise NotImplementedError(f"The '{property.type}' property filter does not work in '{scope}' scope") - operator = cast(Optional[PropertyOperator], property.operator) or PropertyOperator.exact + operator = cast(Optional[PropertyOperator], property.operator) or PropertyOperator.EXACT value = property.value if property.type == "person" and scope != "person": @@ -195,20 +195,20 @@ def property_to_expr( for v in value ] if ( - operator == PropertyOperator.not_icontains - or operator == PropertyOperator.not_regex - or operator == PropertyOperator.is_not + operator == PropertyOperator.NOT_ICONTAINS + or operator == PropertyOperator.NOT_REGEX + or operator == PropertyOperator.IS_NOT ): return ast.And(exprs=exprs) return ast.Or(exprs=exprs) - if operator == PropertyOperator.is_set: + if operator == PropertyOperator.IS_SET: return ast.CompareOperation( op=ast.CompareOperationOp.NotEq, left=field, right=ast.Constant(value=None), ) - elif operator == PropertyOperator.is_not_set: + elif operator == PropertyOperator.IS_NOT_SET: return ast.Or( exprs=[ ast.CompareOperation( @@ -230,19 +230,19 @@ def property_to_expr( ] ) ) - elif operator == PropertyOperator.icontains: + elif operator == PropertyOperator.ICONTAINS: return ast.CompareOperation( op=ast.CompareOperationOp.ILike, left=field, right=ast.Constant(value=f"%{value}%"), ) - elif operator == PropertyOperator.not_icontains: + elif operator == PropertyOperator.NOT_ICONTAINS: return ast.CompareOperation( op=ast.CompareOperationOp.NotILike, left=field, right=ast.Constant(value=f"%{value}%"), ) - elif operator == PropertyOperator.regex: + elif operator == PropertyOperator.REGEX: return ast.Call( name="ifNull", args=[ @@ -250,7 +250,7 @@ def property_to_expr( ast.Constant(value=False), ], ) - elif operator == PropertyOperator.not_regex: + elif operator == PropertyOperator.NOT_REGEX: return ast.Call( name="ifNull", args=[ @@ -265,17 +265,17 @@ def property_to_expr( ast.Constant(value=True), ], ) - elif operator == PropertyOperator.exact or operator == PropertyOperator.is_date_exact: + elif operator == PropertyOperator.EXACT or operator == PropertyOperator.IS_DATE_EXACT: op = ast.CompareOperationOp.Eq - elif operator == PropertyOperator.is_not: + elif operator == PropertyOperator.IS_NOT: op = ast.CompareOperationOp.NotEq - elif operator == PropertyOperator.lt or operator == PropertyOperator.is_date_before: + elif operator == PropertyOperator.LT or operator == PropertyOperator.IS_DATE_BEFORE: op = ast.CompareOperationOp.Lt - elif operator == PropertyOperator.gt or operator == PropertyOperator.is_date_after: + elif operator == PropertyOperator.GT or operator == PropertyOperator.IS_DATE_AFTER: op = ast.CompareOperationOp.Gt - elif operator == PropertyOperator.lte: + elif operator == PropertyOperator.LTE: op = ast.CompareOperationOp.LtEq - elif operator == PropertyOperator.gte: + elif operator == PropertyOperator.GTE: op = ast.CompareOperationOp.GtEq else: raise NotImplementedError(f"PropertyOperator {operator} not implemented") @@ -365,7 +365,7 @@ def property_to_expr( if scope == "person": raise NotImplementedError(f"property_to_expr for scope {scope} not implemented for type '{property.type}'") value = property.value - operator = cast(Optional[PropertyOperator], property.operator) or PropertyOperator.exact + operator = cast(Optional[PropertyOperator], property.operator) or PropertyOperator.EXACT if isinstance(value, list): if len(value) == 1: value = value[0] @@ -385,20 +385,20 @@ def property_to_expr( for v in value ] if ( - operator == PropertyOperator.is_not - or operator == PropertyOperator.not_icontains - or operator == PropertyOperator.not_regex + operator == PropertyOperator.IS_NOT + or operator == PropertyOperator.NOT_ICONTAINS + or operator == PropertyOperator.NOT_REGEX ): return ast.And(exprs=exprs) return ast.Or(exprs=exprs) if property.key == "selector" or property.key == "tag_name": - if operator != PropertyOperator.exact and operator != PropertyOperator.is_not: + if operator != PropertyOperator.EXACT and operator != PropertyOperator.IS_NOT: raise NotImplementedError( f"property_to_expr for element {property.key} only supports exact and is_not operators, not {operator}" ) expr = selector_to_expr(str(value)) if property.key == "selector" else tag_name_to_expr(str(value)) - if operator == PropertyOperator.is_not: + if operator == PropertyOperator.IS_NOT: return ast.Call(name="not", args=[expr]) return expr @@ -445,19 +445,19 @@ def action_to_expr(action: Action) -> ast.Expr: exprs.append(tag_name_to_expr(step.tag_name)) if step.href is not None: if step.href_matching == "regex": - operator = PropertyOperator.regex + operator = PropertyOperator.REGEX elif step.href_matching == "contains": - operator = PropertyOperator.icontains + operator = PropertyOperator.ICONTAINS else: - operator = PropertyOperator.exact + operator = PropertyOperator.EXACT exprs.append(element_chain_key_filter("href", step.href, operator)) if step.text is not None: if step.text_matching == "regex": - operator = PropertyOperator.regex + operator = PropertyOperator.REGEX elif step.text_matching == "contains": - operator = PropertyOperator.icontains + operator = PropertyOperator.ICONTAINS else: - operator = PropertyOperator.exact + operator = PropertyOperator.EXACT exprs.append(element_chain_key_filter("text", step.text, operator)) if step.url: @@ -510,28 +510,28 @@ def entity_to_expr(entity: RetentionEntity) -> ast.Expr: def element_chain_key_filter(key: str, text: str, operator: PropertyOperator): escaped = text.replace('"', r"\"") - if operator == PropertyOperator.is_set or operator == PropertyOperator.is_not_set: + if operator == PropertyOperator.IS_SET or operator == PropertyOperator.IS_NOT_SET: value = r'[^"]+' - elif operator == PropertyOperator.icontains or operator == PropertyOperator.not_icontains: + elif operator == PropertyOperator.ICONTAINS or operator == PropertyOperator.NOT_ICONTAINS: value = rf'[^"]*{re.escape(escaped)}[^"]*' - elif operator == PropertyOperator.regex or operator == PropertyOperator.not_regex: + elif operator == PropertyOperator.REGEX or operator == PropertyOperator.NOT_REGEX: value = escaped - elif operator == PropertyOperator.exact or operator == PropertyOperator.is_not: + elif operator == PropertyOperator.EXACT or operator == PropertyOperator.IS_NOT: value = re.escape(escaped) else: raise NotImplementedError(f"element_href_to_expr not implemented for operator {operator}") regex = f'({key}="{value}")' - if operator == PropertyOperator.icontains or operator == PropertyOperator.not_icontains: + if operator == PropertyOperator.ICONTAINS or operator == PropertyOperator.NOT_ICONTAINS: expr = parse_expr("elements_chain =~* {regex}", {"regex": ast.Constant(value=str(regex))}) else: expr = parse_expr("elements_chain =~ {regex}", {"regex": ast.Constant(value=str(regex))}) if ( - operator == PropertyOperator.is_not_set - or operator == PropertyOperator.not_icontains - or operator == PropertyOperator.is_not - or operator == PropertyOperator.not_regex + operator == PropertyOperator.IS_NOT_SET + or operator == PropertyOperator.NOT_ICONTAINS + or operator == PropertyOperator.IS_NOT + or operator == PropertyOperator.NOT_REGEX ): expr = ast.Call(name="not", args=[expr]) return expr diff --git a/posthog/hogql/test/_test_parser.py b/posthog/hogql/test/_test_parser.py index 4a76240ffdc90..d1bff88ebfcff 100644 --- a/posthog/hogql/test/_test_parser.py +++ b/posthog/hogql/test/_test_parser.py @@ -1409,7 +1409,7 @@ def test_window_functions(self): alias="timestamp", expr=ast.WindowFunction( name="min", - args=[ast.Field(chain=["timestamp"])], + exprs=[ast.Field(chain=["timestamp"])], over_expr=ast.WindowExpr( partition_by=[ast.Field(chain=["person", "id"])], order_by=[ @@ -1429,6 +1429,32 @@ def test_window_functions(self): ) self.assertEqual(expr, expected) + def test_window_functions_call_arg(self): + query = "SELECT quantiles(0.0, 0.25, 0.5, 0.75, 1.0)(distinct distinct_id) over () as values FROM events" + expr = self._select(query) + expected = ast.SelectQuery( + select=[ + ast.Alias( + alias="values", + expr=ast.WindowFunction( + name="quantiles", + args=[ast.Field(chain=["distinct_id"])], + exprs=[ + ast.Constant(value=0.0), + ast.Constant(value=0.25), + ast.Constant(value=0.5), + ast.Constant(value=0.75), + ast.Constant(value=1.0), + ], + over_expr=ast.WindowExpr(), + ), + hidden=False, + ) + ], + select_from=ast.JoinExpr(table=ast.Field(chain=["events"])), + ) + self.assertEqual(expr, expected) + def test_window_functions_with_window(self): query = "SELECT person.id, min(timestamp) over win1 AS timestamp FROM events WINDOW win1 as (PARTITION by person.id ORDER BY timestamp DESC ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING)" expr = self._select(query) @@ -1439,7 +1465,7 @@ def test_window_functions_with_window(self): alias="timestamp", expr=ast.WindowFunction( name="min", - args=[ast.Field(chain=["timestamp"])], + exprs=[ast.Field(chain=["timestamp"])], over_identifier="win1", ), ), diff --git a/posthog/hogql/test/test_modifiers.py b/posthog/hogql/test/test_modifiers.py index b4619cacfbc21..d118f4e32355d 100644 --- a/posthog/hogql/test/test_modifiers.py +++ b/posthog/hogql/test/test_modifiers.py @@ -17,34 +17,34 @@ class TestModifiers(BaseTest): def test_create_default_modifiers_for_team_init(self): assert self.team.person_on_events_mode == "disabled" modifiers = create_default_modifiers_for_team(self.team) - assert modifiers.personsOnEventsMode == PersonsOnEventsMode.disabled # NB! not a None + assert modifiers.personsOnEventsMode == PersonsOnEventsMode.DISABLED # NB! not a None modifiers = create_default_modifiers_for_team( self.team, - HogQLQueryModifiers(personsOnEventsMode=PersonsOnEventsMode.person_id_no_override_properties_on_events), + HogQLQueryModifiers(personsOnEventsMode=PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS), ) - assert modifiers.personsOnEventsMode == PersonsOnEventsMode.person_id_no_override_properties_on_events + assert modifiers.personsOnEventsMode == PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS modifiers = create_default_modifiers_for_team( self.team, - HogQLQueryModifiers(personsOnEventsMode=PersonsOnEventsMode.person_id_override_properties_on_events), + HogQLQueryModifiers(personsOnEventsMode=PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS), ) - assert modifiers.personsOnEventsMode == PersonsOnEventsMode.person_id_override_properties_on_events + assert modifiers.personsOnEventsMode == PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS def test_team_modifiers_override(self): assert self.team.modifiers is None modifiers = create_default_modifiers_for_team(self.team) assert modifiers.personsOnEventsMode == self.team.default_modifiers["personsOnEventsMode"] - assert modifiers.personsOnEventsMode == PersonsOnEventsMode.disabled # the default mode + assert modifiers.personsOnEventsMode == PersonsOnEventsMode.DISABLED # the default mode - self.team.modifiers = {"personsOnEventsMode": PersonsOnEventsMode.person_id_override_properties_on_events} + self.team.modifiers = {"personsOnEventsMode": PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS} self.team.save() modifiers = create_default_modifiers_for_team(self.team) - assert modifiers.personsOnEventsMode == PersonsOnEventsMode.person_id_override_properties_on_events - assert self.team.default_modifiers["personsOnEventsMode"] == PersonsOnEventsMode.disabled # no change here + assert modifiers.personsOnEventsMode == PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS + assert self.team.default_modifiers["personsOnEventsMode"] == PersonsOnEventsMode.DISABLED # no change here - self.team.modifiers = {"personsOnEventsMode": PersonsOnEventsMode.person_id_no_override_properties_on_events} + self.team.modifiers = {"personsOnEventsMode": PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS} self.team.save() modifiers = create_default_modifiers_for_team(self.team) - assert modifiers.personsOnEventsMode == PersonsOnEventsMode.person_id_no_override_properties_on_events + assert modifiers.personsOnEventsMode == PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS def test_modifiers_persons_on_events_mode_person_id_override_properties_on_events(self): query = "SELECT event, person_id FROM events" @@ -53,7 +53,7 @@ def test_modifiers_persons_on_events_mode_person_id_override_properties_on_event response = execute_hogql_query( query, team=self.team, - modifiers=HogQLQueryModifiers(personsOnEventsMode=PersonsOnEventsMode.disabled), + modifiers=HogQLQueryModifiers(personsOnEventsMode=PersonsOnEventsMode.DISABLED), ) assert " JOIN " in response.clickhouse @@ -62,7 +62,7 @@ def test_modifiers_persons_on_events_mode_person_id_override_properties_on_event query, team=self.team, modifiers=HogQLQueryModifiers( - personsOnEventsMode=PersonsOnEventsMode.person_id_no_override_properties_on_events + personsOnEventsMode=PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS ), ) assert " JOIN " not in response.clickhouse @@ -77,7 +77,7 @@ class TestCase(NamedTuple): test_cases: list[TestCase] = [ TestCase( - PersonsOnEventsMode.disabled, + PersonsOnEventsMode.DISABLED, [ "events.event AS event", "events__pdi__person.id AS id", @@ -86,7 +86,7 @@ class TestCase(NamedTuple): ], ), TestCase( - PersonsOnEventsMode.person_id_no_override_properties_on_events, + PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS, [ "events.event AS event", "events.person_id AS id", @@ -95,7 +95,7 @@ class TestCase(NamedTuple): ], ), TestCase( - PersonsOnEventsMode.person_id_override_properties_on_events, + PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS, [ "events.event AS event", "if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id) AS id", @@ -107,7 +107,7 @@ class TestCase(NamedTuple): ], ), TestCase( - PersonsOnEventsMode.person_id_override_properties_joined, + PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_JOINED, [ "events.event AS event", "events__person.id AS id", @@ -142,7 +142,7 @@ def test_modifiers_persons_argmax_version_v2(self): response = execute_hogql_query( query, team=self.team, - modifiers=HogQLQueryModifiers(personsArgMaxVersion=PersonsArgMaxVersion.v1), + modifiers=HogQLQueryModifiers(personsArgMaxVersion=PersonsArgMaxVersion.V1), ) assert "in(tuple(person.id, person.version)" not in response.clickhouse @@ -150,7 +150,7 @@ def test_modifiers_persons_argmax_version_v2(self): response = execute_hogql_query( query, team=self.team, - modifiers=HogQLQueryModifiers(personsArgMaxVersion=PersonsArgMaxVersion.v2), + modifiers=HogQLQueryModifiers(personsArgMaxVersion=PersonsArgMaxVersion.V2), ) assert "in(tuple(person.id, person.version)" in response.clickhouse @@ -159,7 +159,7 @@ def test_modifiers_persons_argmax_version_auto(self): response = execute_hogql_query( "SELECT id, properties.$browser, is_identified FROM persons", team=self.team, - modifiers=HogQLQueryModifiers(personsArgMaxVersion=PersonsArgMaxVersion.auto), + modifiers=HogQLQueryModifiers(personsArgMaxVersion=PersonsArgMaxVersion.AUTO), ) assert "in(tuple(person.id, person.version)" in response.clickhouse @@ -167,7 +167,7 @@ def test_modifiers_persons_argmax_version_auto(self): response = execute_hogql_query( "SELECT id, properties FROM persons", team=self.team, - modifiers=HogQLQueryModifiers(personsArgMaxVersion=PersonsArgMaxVersion.auto), + modifiers=HogQLQueryModifiers(personsArgMaxVersion=PersonsArgMaxVersion.AUTO), ) assert "in(tuple(person.id, person.version)" in response.clickhouse @@ -175,7 +175,7 @@ def test_modifiers_persons_argmax_version_auto(self): response = execute_hogql_query( "SELECT id, is_identified FROM persons", team=self.team, - modifiers=HogQLQueryModifiers(personsArgMaxVersion=PersonsArgMaxVersion.auto), + modifiers=HogQLQueryModifiers(personsArgMaxVersion=PersonsArgMaxVersion.AUTO), ) assert "in(tuple(person.id, person.version)" not in response.clickhouse @@ -208,7 +208,7 @@ def test_modifiers_materialization_mode(self): response = execute_hogql_query( "SELECT properties.$browser FROM events", team=self.team, - modifiers=HogQLQueryModifiers(materializationMode=MaterializationMode.auto), + modifiers=HogQLQueryModifiers(materializationMode=MaterializationMode.AUTO), pretty=False, ) assert ( @@ -218,7 +218,7 @@ def test_modifiers_materialization_mode(self): response = execute_hogql_query( "SELECT properties.$browser FROM events", team=self.team, - modifiers=HogQLQueryModifiers(materializationMode=MaterializationMode.legacy_null_as_null), + modifiers=HogQLQueryModifiers(materializationMode=MaterializationMode.LEGACY_NULL_AS_NULL), pretty=False, ) assert ( @@ -228,7 +228,7 @@ def test_modifiers_materialization_mode(self): response = execute_hogql_query( "SELECT properties.$browser FROM events", team=self.team, - modifiers=HogQLQueryModifiers(materializationMode=MaterializationMode.legacy_null_as_string), + modifiers=HogQLQueryModifiers(materializationMode=MaterializationMode.LEGACY_NULL_AS_STRING), pretty=False, ) assert "SELECT nullIf(events.`mat_$browser`, '') AS `$browser` FROM events" in response.clickhouse @@ -236,7 +236,7 @@ def test_modifiers_materialization_mode(self): response = execute_hogql_query( "SELECT properties.$browser FROM events", team=self.team, - modifiers=HogQLQueryModifiers(materializationMode=MaterializationMode.disabled), + modifiers=HogQLQueryModifiers(materializationMode=MaterializationMode.DISABLED), pretty=False, ) assert ( diff --git a/posthog/hogql/test/test_printer.py b/posthog/hogql/test/test_printer.py index c8466c4f40387..5bf05573e671f 100644 --- a/posthog/hogql/test/test_printer.py +++ b/posthog/hogql/test/test_printer.py @@ -141,7 +141,7 @@ def test_fields_and_properties(self): context = HogQLContext( team_id=self.team.pk, within_non_hogql_query=True, - modifiers=HogQLQueryModifiers(personsOnEventsMode=PersonsOnEventsMode.disabled), + modifiers=HogQLQueryModifiers(personsOnEventsMode=PersonsOnEventsMode.DISABLED), ) self.assertEqual( self._expr("person.properties.bla", context), @@ -158,7 +158,7 @@ def test_fields_and_properties(self): team_id=self.team.pk, within_non_hogql_query=True, modifiers=HogQLQueryModifiers( - personsOnEventsMode=PersonsOnEventsMode.person_id_no_override_properties_on_events + personsOnEventsMode=PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS ), ) self.assertEqual( @@ -749,7 +749,7 @@ def test_select_sample(self): context = HogQLContext( team_id=self.team.pk, enable_select_queries=True, - modifiers=HogQLQueryModifiers(personsArgMaxVersion=PersonsArgMaxVersion.v2), + modifiers=HogQLQueryModifiers(personsArgMaxVersion=PersonsArgMaxVersion.V2), ) query = self._select( "SELECT events.event FROM events SAMPLE 2/78 OFFSET 999 JOIN persons ON persons.id=events.person_id", @@ -772,7 +772,7 @@ def test_select_sample(self): context = HogQLContext( team_id=self.team.pk, enable_select_queries=True, - modifiers=HogQLQueryModifiers(personsArgMaxVersion=PersonsArgMaxVersion.v2), + modifiers=HogQLQueryModifiers(personsArgMaxVersion=PersonsArgMaxVersion.V2), ) self.assertEqual( self._select( @@ -795,7 +795,7 @@ def test_select_sample(self): context = HogQLContext( team_id=self.team.pk, enable_select_queries=True, - modifiers=HogQLQueryModifiers(personsArgMaxVersion=PersonsArgMaxVersion.v2), + modifiers=HogQLQueryModifiers(personsArgMaxVersion=PersonsArgMaxVersion.V2), ) expected = self._select( "SELECT events.event FROM events SAMPLE 2/78 OFFSET 999 JOIN persons ON persons.id=events.person_id", @@ -814,7 +814,7 @@ def test_select_sample(self): context = HogQLContext( team_id=self.team.pk, enable_select_queries=True, - modifiers=HogQLQueryModifiers(personsArgMaxVersion=PersonsArgMaxVersion.v2), + modifiers=HogQLQueryModifiers(personsArgMaxVersion=PersonsArgMaxVersion.V2), ) expected = self._select( "SELECT events.event FROM events SAMPLE 2/78 OFFSET 999 JOIN persons SAMPLE 0.1 ON persons.id=events.person_id", @@ -925,6 +925,14 @@ def test_window_functions_with_window(self): f"SELECT events.distinct_id AS distinct_id, min(toTimeZone(events.timestamp, %(hogql_val_0)s)) OVER win1 AS timestamp FROM events WHERE equals(events.team_id, {self.team.pk}) WINDOW win1 AS (PARTITION BY events.distinct_id ORDER BY timestamp DESC ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) LIMIT {MAX_SELECT_RETURNED_ROWS}", ) + def test_window_functions_with_arg(self): + self.assertEqual( + self._select( + "SELECT quantiles(0.0, 0.25, 0.5, 0.75, 1.0)(distinct distinct_id) over () as values FROM events" + ), + f"SELECT quantiles(0.0, 0.25, 0.5, 0.75, 1.0)(events.distinct_id) OVER () AS values FROM events WHERE equals(events.team_id, {self.team.pk}) LIMIT 50000", + ) + def test_nullish_concat(self): self.assertEqual( self._expr("concat(null, 'a', 3, toString(4), toString(NULL))"), diff --git a/posthog/hogql/test/test_property.py b/posthog/hogql/test/test_property.py index 03c42efb3673b..7a4edb8a3db23 100644 --- a/posthog/hogql/test/test_property.py +++ b/posthog/hogql/test/test_property.py @@ -336,7 +336,7 @@ def test_property_to_expr_element(self): "operator": "exact", } ), - clear_locations(element_chain_key_filter("href", "href-text.", PropertyOperator.exact)), + clear_locations(element_chain_key_filter("href", "href-text.", PropertyOperator.EXACT)), ) self.assertEqual( self._property_to_expr( @@ -347,7 +347,7 @@ def test_property_to_expr_element(self): "operator": "regex", } ), - clear_locations(element_chain_key_filter("text", "text-text.", PropertyOperator.regex)), + clear_locations(element_chain_key_filter("text", "text-text.", PropertyOperator.REGEX)), ) def test_property_groups(self): @@ -503,35 +503,35 @@ def test_selector_to_expr(self): def test_elements_chain_key_filter(self): self.assertEqual( - clear_locations(element_chain_key_filter("href", "boo..", PropertyOperator.is_set)), + clear_locations(element_chain_key_filter("href", "boo..", PropertyOperator.IS_SET)), clear_locations(elements_chain_match('(href="[^"]+")')), ) self.assertEqual( - clear_locations(element_chain_key_filter("href", "boo..", PropertyOperator.is_not_set)), + clear_locations(element_chain_key_filter("href", "boo..", PropertyOperator.IS_NOT_SET)), clear_locations(not_call(elements_chain_match('(href="[^"]+")'))), ) self.assertEqual( - clear_locations(element_chain_key_filter("href", "boo..", PropertyOperator.icontains)), + clear_locations(element_chain_key_filter("href", "boo..", PropertyOperator.ICONTAINS)), clear_locations(elements_chain_imatch('(href="[^"]*boo\\.\\.[^"]*")')), ) self.assertEqual( - clear_locations(element_chain_key_filter("href", "boo..", PropertyOperator.not_icontains)), + clear_locations(element_chain_key_filter("href", "boo..", PropertyOperator.NOT_ICONTAINS)), clear_locations(not_call(elements_chain_imatch('(href="[^"]*boo\\.\\.[^"]*")'))), ) self.assertEqual( - clear_locations(element_chain_key_filter("href", "boo..", PropertyOperator.regex)), + clear_locations(element_chain_key_filter("href", "boo..", PropertyOperator.REGEX)), clear_locations(elements_chain_match('(href="boo..")')), ) self.assertEqual( - clear_locations(element_chain_key_filter("href", "boo..", PropertyOperator.not_regex)), + clear_locations(element_chain_key_filter("href", "boo..", PropertyOperator.NOT_REGEX)), clear_locations(not_call(elements_chain_match('(href="boo..")'))), ) self.assertEqual( - clear_locations(element_chain_key_filter("href", "boo..", PropertyOperator.exact)), + clear_locations(element_chain_key_filter("href", "boo..", PropertyOperator.EXACT)), clear_locations(elements_chain_match('(href="boo\\.\\.")')), ) self.assertEqual( - clear_locations(element_chain_key_filter("href", "boo..", PropertyOperator.is_not)), + clear_locations(element_chain_key_filter("href", "boo..", PropertyOperator.IS_NOT)), clear_locations(not_call(elements_chain_match('(href="boo\\.\\.")'))), ) diff --git a/posthog/hogql/transforms/property_types.py b/posthog/hogql/transforms/property_types.py index 58c8539533acd..26ab9fc8b3656 100644 --- a/posthog/hogql/transforms/property_types.py +++ b/posthog/hogql/transforms/property_types.py @@ -236,7 +236,7 @@ def _add_property_notice( ): property_name = str(node.chain[-1]) if property_type == "person": - if self.context.modifiers.personsOnEventsMode != PersonsOnEventsMode.disabled: + if self.context.modifiers.personsOnEventsMode != PersonsOnEventsMode.DISABLED: materialized_column = self._get_materialized_column("events", property_name, "person_properties") else: materialized_column = self._get_materialized_column("person", property_name, "properties") diff --git a/posthog/hogql/transforms/test/test_in_cohort.py b/posthog/hogql/transforms/test/test_in_cohort.py index cde9e291c43c0..bdceb43df7932 100644 --- a/posthog/hogql/transforms/test/test_in_cohort.py +++ b/posthog/hogql/transforms/test/test_in_cohort.py @@ -48,7 +48,7 @@ def test_in_cohort_dynamic(self): response = execute_hogql_query( f"SELECT event FROM events WHERE person_id IN COHORT {cohort.pk} AND event='{random_uuid}'", self.team, - modifiers=HogQLQueryModifiers(inCohortVia=InCohortVia.leftjoin), + modifiers=HogQLQueryModifiers(inCohortVia=InCohortVia.LEFTJOIN), pretty=False, ) assert pretty_print_response_in_tests(response, self.team.pk) == self.snapshot # type: ignore @@ -65,7 +65,7 @@ def test_in_cohort_static(self): response = execute_hogql_query( f"SELECT event FROM events WHERE person_id IN COHORT {cohort.pk}", self.team, - modifiers=HogQLQueryModifiers(inCohortVia=InCohortVia.leftjoin), + modifiers=HogQLQueryModifiers(inCohortVia=InCohortVia.LEFTJOIN), pretty=False, ) assert pretty_print_response_in_tests(response, self.team.pk) == self.snapshot # type: ignore @@ -81,7 +81,7 @@ def test_in_cohort_strings(self): response = execute_hogql_query( f"SELECT event FROM events WHERE person_id IN COHORT 'my cohort'", self.team, - modifiers=HogQLQueryModifiers(inCohortVia=InCohortVia.leftjoin), + modifiers=HogQLQueryModifiers(inCohortVia=InCohortVia.LEFTJOIN), pretty=False, ) assert pretty_print_response_in_tests(response, self.team.pk) == self.snapshot # type: ignore @@ -93,7 +93,7 @@ def test_in_cohort_error(self): execute_hogql_query( f"SELECT event FROM events WHERE person_id IN COHORT true", self.team, - modifiers=HogQLQueryModifiers(inCohortVia=InCohortVia.subquery), + modifiers=HogQLQueryModifiers(inCohortVia=InCohortVia.SUBQUERY), pretty=False, ) self.assertEqual(str(e.exception), "cohort() takes exactly one string or integer argument") @@ -102,7 +102,7 @@ def test_in_cohort_error(self): execute_hogql_query( f"SELECT event FROM events WHERE person_id IN COHORT 'blabla'", self.team, - modifiers=HogQLQueryModifiers(inCohortVia=InCohortVia.subquery), + modifiers=HogQLQueryModifiers(inCohortVia=InCohortVia.SUBQUERY), pretty=False, ) self.assertEqual(str(e.exception), "Could not find a cohort with the name 'blabla'") @@ -118,7 +118,7 @@ def test_in_cohort_conjoined_string(self): response = execute_hogql_query( f"SELECT event FROM events WHERE person_id IN COHORT 'my cohort'", self.team, - modifiers=HogQLQueryModifiers(inCohortVia=InCohortVia.leftjoin_conjoined), + modifiers=HogQLQueryModifiers(inCohortVia=InCohortVia.LEFTJOIN_CONJOINED), pretty=False, ) assert pretty_print_response_in_tests(response, self.team.pk) == self.snapshot # type: ignore @@ -133,7 +133,7 @@ def test_in_cohort_conjoined_int(self): response = execute_hogql_query( f"SELECT event FROM events WHERE person_id IN COHORT {cohort.pk}", self.team, - modifiers=HogQLQueryModifiers(inCohortVia=InCohortVia.leftjoin_conjoined), + modifiers=HogQLQueryModifiers(inCohortVia=InCohortVia.LEFTJOIN_CONJOINED), pretty=False, ) assert pretty_print_response_in_tests(response, self.team.pk) == self.snapshot # type: ignore @@ -150,7 +150,7 @@ def test_in_cohort_conjoined_dynamic(self): response = execute_hogql_query( f"SELECT event FROM events WHERE person_id IN COHORT {cohort.pk} AND event='{random_uuid}'", self.team, - modifiers=HogQLQueryModifiers(inCohortVia=InCohortVia.leftjoin_conjoined), + modifiers=HogQLQueryModifiers(inCohortVia=InCohortVia.LEFTJOIN_CONJOINED), pretty=False, ) assert pretty_print_response_in_tests(response, self.team.pk) == self.snapshot # type: ignore @@ -164,7 +164,7 @@ def test_in_cohort_conjoined_error(self): execute_hogql_query( f"SELECT event FROM events WHERE person_id IN COHORT true", self.team, - modifiers=HogQLQueryModifiers(inCohortVia=InCohortVia.leftjoin_conjoined), + modifiers=HogQLQueryModifiers(inCohortVia=InCohortVia.LEFTJOIN_CONJOINED), pretty=False, ) self.assertEqual(str(e.exception), "cohort() takes exactly one string or integer argument") @@ -173,7 +173,7 @@ def test_in_cohort_conjoined_error(self): execute_hogql_query( f"SELECT event FROM events WHERE person_id IN COHORT 'blabla'", self.team, - modifiers=HogQLQueryModifiers(inCohortVia=InCohortVia.leftjoin_conjoined), + modifiers=HogQLQueryModifiers(inCohortVia=InCohortVia.LEFTJOIN_CONJOINED), pretty=False, ) self.assertEqual(str(e.exception), "Could not find a cohort with the name 'blabla'") diff --git a/posthog/hogql/transforms/test/test_lazy_tables.py b/posthog/hogql/transforms/test/test_lazy_tables.py index 3ea762b786983..2b5cabb7d0d4e 100644 --- a/posthog/hogql/transforms/test/test_lazy_tables.py +++ b/posthog/hogql/transforms/test/test_lazy_tables.py @@ -142,7 +142,7 @@ def test_resolve_lazy_table_indirectly_referenced(self): # of a lazy join. printed = self._print_select( "select person.id from events", - HogQLQueryModifiers(personsOnEventsMode=PersonsOnEventsMode.person_id_override_properties_joined), + HogQLQueryModifiers(personsOnEventsMode=PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_JOINED), ) assert printed == self.snapshot @@ -152,6 +152,6 @@ def test_resolve_lazy_table_indirect_duplicate_references(self): # is referenced via two different selected columns. printed = self._print_select( "select person_id, person.properties from events", - HogQLQueryModifiers(personsOnEventsMode=PersonsOnEventsMode.person_id_override_properties_joined), + HogQLQueryModifiers(personsOnEventsMode=PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_JOINED), ) assert printed == self.snapshot diff --git a/posthog/hogql/visitor.py b/posthog/hogql/visitor.py index 84b34bddbf8c4..f4d8c6f308c20 100644 --- a/posthog/hogql/visitor.py +++ b/posthog/hogql/visitor.py @@ -246,8 +246,10 @@ def visit_window_expr(self, node: ast.WindowExpr): self.visit(node.frame_end) def visit_window_function(self, node: ast.WindowFunction): - for expr in node.args or []: + for expr in node.exprs or []: self.visit(expr) + for arg in node.args or []: + self.visit(arg) self.visit(node.over_expr) def visit_window_frame_expr(self, node: ast.WindowFrameExpr): @@ -553,7 +555,8 @@ def visit_window_function(self, node: ast.WindowFunction): end=None if self.clear_locations else node.end, type=None if self.clear_types else node.type, name=node.name, - args=[self.visit(expr) for expr in node.args] if node.args else None, + exprs=[self.visit(expr) for expr in node.exprs] if node.exprs else None, + args=[self.visit(arg) for arg in node.args] if node.args else None, over_expr=self.visit(node.over_expr) if node.over_expr else None, over_identifier=node.over_identifier, ) diff --git a/posthog/hogql_queries/apply_dashboard_filters.py b/posthog/hogql_queries/apply_dashboard_filters.py index 64302522586ea..6d8e74f0fb588 100644 --- a/posthog/hogql_queries/apply_dashboard_filters.py +++ b/posthog/hogql_queries/apply_dashboard_filters.py @@ -3,7 +3,7 @@ from posthog.models import Team from posthog.schema import DashboardFilter, NodeKind -WRAPPER_NODE_KINDS = [NodeKind.DataTableNode, NodeKind.DataVisualizationNode, NodeKind.InsightVizNode] +WRAPPER_NODE_KINDS = [NodeKind.DATA_TABLE_NODE, NodeKind.DATA_VISUALIZATION_NODE, NodeKind.INSIGHT_VIZ_NODE] # Apply the filters from the django-style Dashboard object diff --git a/posthog/hogql_queries/insights/funnels/base.py b/posthog/hogql_queries/insights/funnels/base.py index 104a2aa87d580..e673f59c467ef 100644 --- a/posthog/hogql_queries/insights/funnels/base.py +++ b/posthog/hogql_queries/insights/funnels/base.py @@ -119,7 +119,7 @@ def _get_breakdown_select_prop(self) -> list[ast.Expr]: prop_basic = ast.Alias(alias="prop_basic", expr=self._get_breakdown_expr()) # breakdown attribution - if breakdownAttributionType == BreakdownAttributionType.step: + if breakdownAttributionType == BreakdownAttributionType.STEP: select_columns = [] default_breakdown_selector = "[]" if self._query_has_array_breakdown() else "NULL" # get prop value from each step @@ -133,8 +133,8 @@ def _get_breakdown_select_prop(self) -> list[ast.Expr]: return [prop_basic, *select_columns, final_select, prop_window] elif breakdownAttributionType in [ - BreakdownAttributionType.first_touch, - BreakdownAttributionType.last_touch, + BreakdownAttributionType.FIRST_TOUCH, + BreakdownAttributionType.LAST_TOUCH, ]: prop_conditional = ( "notEmpty(arrayFilter(x -> notEmpty(x), prop))" @@ -143,7 +143,7 @@ def _get_breakdown_select_prop(self) -> list[ast.Expr]: ) aggregate_operation = ( - "argMinIf" if breakdownAttributionType == BreakdownAttributionType.first_touch else "argMaxIf" + "argMinIf" if breakdownAttributionType == BreakdownAttributionType.FIRST_TOUCH else "argMaxIf" ) breakdown_window_selector = f"{aggregate_operation}(prop, timestamp, {prop_conditional})" @@ -368,7 +368,7 @@ def _get_inner_event_query( funnel_events_query.select = [*funnel_events_query.select, *all_step_cols] - if breakdown and breakdownType == BreakdownType.cohort: + if breakdown and breakdownType == BreakdownType.COHORT: if funnel_events_query.select_from is None: raise ValidationError("Apologies, there was an error adding cohort breakdowns to the query.") funnel_events_query.select_from.next_join = self._get_cohort_breakdown_join() @@ -378,7 +378,7 @@ def _get_inner_event_query( steps_conditions = self._get_steps_conditions(length=len(entities_to_use)) funnel_events_query.where = ast.And(exprs=[funnel_events_query.where, steps_conditions]) - if breakdown and breakdownAttributionType != BreakdownAttributionType.all_events: + if breakdown and breakdownAttributionType != BreakdownAttributionType.ALL_EVENTS: # ALL_EVENTS attribution is the old default, which doesn't need the subquery return self._add_breakdown_attribution_subquery(funnel_events_query) @@ -425,8 +425,8 @@ def _add_breakdown_attribution_subquery(self, inner_query: ast.SelectQuery) -> a ) if breakdownAttributionType in [ - BreakdownAttributionType.first_touch, - BreakdownAttributionType.last_touch, + BreakdownAttributionType.FIRST_TOUCH, + BreakdownAttributionType.LAST_TOUCH, ]: # When breaking down by first/last touch, each person can only have one prop value # so just select that. Except for the empty case, where we select the default. @@ -987,9 +987,9 @@ def _get_step_counts_query(self, outer_select: list[ast.Expr], inner_select: lis *person_and_group_properties, ] if breakdown and breakdownType in [ - BreakdownType.person, - BreakdownType.event, - BreakdownType.group, + BreakdownType.PERSON, + BreakdownType.EVENT, + BreakdownType.GROUP, ]: time_fields = [ parse_expr(f"min(step_{i}_conversion_time) as step_{i}_conversion_time") for i in range(1, max_steps) diff --git a/posthog/hogql_queries/insights/funnels/funnel.py b/posthog/hogql_queries/insights/funnels/funnel.py index c44108dbc61ea..470979d32b26d 100644 --- a/posthog/hogql_queries/insights/funnels/funnel.py +++ b/posthog/hogql_queries/insights/funnels/funnel.py @@ -33,9 +33,9 @@ def get_query(self): max_steps = self.context.max_steps if self.context.breakdown and self.context.breakdownType in [ - BreakdownType.person, - BreakdownType.event, - BreakdownType.group, + BreakdownType.PERSON, + BreakdownType.EVENT, + BreakdownType.GROUP, ]: return self._breakdown_other_subquery() diff --git a/posthog/hogql_queries/insights/funnels/funnel_correlation_query_runner.py b/posthog/hogql_queries/insights/funnels/funnel_correlation_query_runner.py index fc6bc0cc5657c..33026970c1e2e 100644 --- a/posthog/hogql_queries/insights/funnels/funnel_correlation_query_runner.py +++ b/posthog/hogql_queries/insights/funnels/funnel_correlation_query_runner.py @@ -303,7 +303,7 @@ def serialize_event_odds_ratio(self, odds_ratio: EventOddsRatio) -> EventOddsRat failure_count=odds_ratio["failure_count"], odds_ratio=odds_ratio["odds_ratio"], correlation_type=( - CorrelationType.success if odds_ratio["correlation_type"] == "success" else CorrelationType.failure + CorrelationType.SUCCESS if odds_ratio["correlation_type"] == "success" else CorrelationType.FAILURE ), event=event_definition, ) @@ -334,10 +334,10 @@ def to_query(self) -> ast.SelectQuery | ast.SelectUnionQuery: Returns a query string and params, which are used to generate the contingency table. The query returns success and failure count for event / property values, along with total success and failure counts. """ - if self.query.funnelCorrelationType == FunnelCorrelationResultsType.properties: + if self.query.funnelCorrelationType == FunnelCorrelationResultsType.PROPERTIES: return self.get_properties_query() - if self.query.funnelCorrelationType == FunnelCorrelationResultsType.event_with_properties: + if self.query.funnelCorrelationType == FunnelCorrelationResultsType.EVENT_WITH_PROPERTIES: return self.get_event_property_query() return self.get_event_query() @@ -345,7 +345,7 @@ def to_query(self) -> ast.SelectQuery | ast.SelectUnionQuery: def to_actors_query(self) -> ast.SelectQuery | ast.SelectUnionQuery: assert self.correlation_actors_query is not None - if self.query.funnelCorrelationType == FunnelCorrelationResultsType.properties: + if self.query.funnelCorrelationType == FunnelCorrelationResultsType.PROPERTIES: # Filtering on persons / groups properties can be pushed down to funnel events query if ( self.correlation_actors_query.funnelCorrelationPropertyValues @@ -841,7 +841,7 @@ def _get_funnel_step_names(self) -> list[str]: def properties_to_include(self) -> list[str]: props_to_include: list[str] = [] # TODO: implement or remove - # if self.query.funnelCorrelationType == FunnelCorrelationResultsType.properties: + # if self.query.funnelCorrelationType == FunnelCorrelationResultsType.PROPERTIES: # assert self.query.funnelCorrelationNames is not None # # When dealing with properties, make sure funnel response comes with properties @@ -859,7 +859,7 @@ def properties_to_include(self) -> list[str]: def support_autocapture_elements(self) -> bool: if ( - self.query.funnelCorrelationType == FunnelCorrelationResultsType.event_with_properties + self.query.funnelCorrelationType == FunnelCorrelationResultsType.EVENT_WITH_PROPERTIES and AUTOCAPTURE_EVENT in (self.query.funnelCorrelationEventNames or []) ): return True diff --git a/posthog/hogql_queries/insights/funnels/funnel_query_context.py b/posthog/hogql_queries/insights/funnels/funnel_query_context.py index 499dc3eb9ed4c..14ec8ba4d1624 100644 --- a/posthog/hogql_queries/insights/funnels/funnel_query_context.py +++ b/posthog/hogql_queries/insights/funnels/funnel_query_context.py @@ -57,15 +57,15 @@ def __init__( self.breakdownFilter = self.query.breakdownFilter or BreakdownFilter() # defaults - self.interval = self.query.interval or IntervalType.day + self.interval = self.query.interval or IntervalType.DAY - self.breakdownType = self.breakdownFilter.breakdown_type or BreakdownType.event + self.breakdownType = self.breakdownFilter.breakdown_type or BreakdownType.EVENT self.breakdownAttributionType = ( - self.funnelsFilter.breakdownAttributionType or BreakdownAttributionType.first_touch + self.funnelsFilter.breakdownAttributionType or BreakdownAttributionType.FIRST_TOUCH ) self.funnelWindowInterval = self.funnelsFilter.funnelWindowInterval or 14 self.funnelWindowIntervalUnit = ( - self.funnelsFilter.funnelWindowIntervalUnit or FunnelConversionWindowTimeUnit.day + self.funnelsFilter.funnelWindowIntervalUnit or FunnelConversionWindowTimeUnit.DAY ) self.includeTimestamp = include_timestamp diff --git a/posthog/hogql_queries/insights/funnels/funnel_strict.py b/posthog/hogql_queries/insights/funnels/funnel_strict.py index a9e60361514e5..4bf9b5ce19ea1 100644 --- a/posthog/hogql_queries/insights/funnels/funnel_strict.py +++ b/posthog/hogql_queries/insights/funnels/funnel_strict.py @@ -9,9 +9,9 @@ def get_query(self): max_steps = self.context.max_steps if self.context.breakdown and self.context.breakdownType in [ - BreakdownType.person, - BreakdownType.event, - BreakdownType.group, + BreakdownType.PERSON, + BreakdownType.EVENT, + BreakdownType.GROUP, ]: return self._breakdown_other_subquery() diff --git a/posthog/hogql_queries/insights/funnels/funnel_unordered.py b/posthog/hogql_queries/insights/funnels/funnel_unordered.py index 3cc68af391427..2bc3be1f8ca81 100644 --- a/posthog/hogql_queries/insights/funnels/funnel_unordered.py +++ b/posthog/hogql_queries/insights/funnels/funnel_unordered.py @@ -44,9 +44,9 @@ def get_query(self): raise ValidationError("Partial Exclusions not allowed in unordered funnels") if self.context.breakdown and self.context.breakdownType in [ - BreakdownType.person, - BreakdownType.event, - BreakdownType.group, + BreakdownType.PERSON, + BreakdownType.EVENT, + BreakdownType.GROUP, ]: return self._breakdown_other_subquery() diff --git a/posthog/hogql_queries/insights/funnels/funnels_query_runner.py b/posthog/hogql_queries/insights/funnels/funnels_query_runner.py index c6b8660132dab..1b85d8858302e 100644 --- a/posthog/hogql_queries/insights/funnels/funnels_query_runner.py +++ b/posthog/hogql_queries/insights/funnels/funnels_query_runner.py @@ -112,9 +112,9 @@ def funnel_order_class(self): def funnel_class(self): funnelVizType = self.context.funnelsFilter.funnelVizType - if funnelVizType == FunnelVizType.trends: + if funnelVizType == FunnelVizType.TRENDS: return FunnelTrends(context=self.context, **self.kwargs) - elif funnelVizType == FunnelVizType.time_to_convert: + elif funnelVizType == FunnelVizType.TIME_TO_CONVERT: return FunnelTimeToConvert(context=self.context) else: return self.funnel_order_class diff --git a/posthog/hogql_queries/insights/funnels/test/test_funnel_correlation.py b/posthog/hogql_queries/insights/funnels/test/test_funnel_correlation.py index bd5db4b9235a9..75daa35aa5cc5 100644 --- a/posthog/hogql_queries/insights/funnels/test/test_funnel_correlation.py +++ b/posthog/hogql_queries/insights/funnels/test/test_funnel_correlation.py @@ -55,7 +55,7 @@ class TestClickhouseFunnelCorrelation(ClickhouseTestMixin, APIBaseTest): def _get_events_for_filters( self, filters, - funnelCorrelationType=FunnelCorrelationResultsType.events, + funnelCorrelationType=FunnelCorrelationResultsType.EVENTS, funnelCorrelationNames=None, funnelCorrelationExcludeNames=None, funnelCorrelationExcludeEventNames=None, @@ -90,10 +90,10 @@ def _get_actors_for_property( ): funnelCorrelationPropertyValues = [ ( - PersonPropertyFilter(key=prop, value=value, operator=PropertyOperator.exact) + PersonPropertyFilter(key=prop, value=value, operator=PropertyOperator.EXACT) if type == "person" else GroupPropertyFilter( - key=prop, value=value, group_type_index=group_type_index, operator=PropertyOperator.exact + key=prop, value=value, group_type_index=group_type_index, operator=PropertyOperator.EXACT ) ) for prop, value, type, group_type_index in property_values @@ -102,7 +102,7 @@ def _get_actors_for_property( serialized_actors = get_actors( filters, self.team, - funnelCorrelationType=FunnelCorrelationResultsType.properties, + funnelCorrelationType=FunnelCorrelationResultsType.PROPERTIES, funnelCorrelationNames=funnelCorrelationNames, funnelCorrelationPersonConverted=success, funnelCorrelationPropertyValues=funnelCorrelationPropertyValues, @@ -158,7 +158,7 @@ def test_basic_funnel_correlation_with_events(self): timestamp="2020-01-03T14:00:00Z", ) - result, _ = self._get_events_for_filters(filters, funnelCorrelationType=FunnelCorrelationResultsType.events) + result, _ = self._get_events_for_filters(filters, funnelCorrelationType=FunnelCorrelationResultsType.EVENTS) odds_ratios = [item.pop("odds_ratio") for item in result] expected_odds_ratios = [11, 1 / 11] @@ -200,7 +200,7 @@ def test_basic_funnel_correlation_with_events(self): # Now exclude positively_related result, _ = self._get_events_for_filters( filters, - funnelCorrelationType=FunnelCorrelationResultsType.events, + funnelCorrelationType=FunnelCorrelationResultsType.EVENTS, funnelCorrelationExcludeEventNames=["positively_related"], ) @@ -417,7 +417,7 @@ def test_funnel_correlation_with_events_and_groups(self): "aggregation_group_type_index": 0, } - result, _ = self._get_events_for_filters(filters, funnelCorrelationType=FunnelCorrelationResultsType.events) + result, _ = self._get_events_for_filters(filters, funnelCorrelationType=FunnelCorrelationResultsType.EVENTS) odds_ratios = [item.pop("odds_ratio") for item in result] expected_odds_ratios = [12 / 7, 1 / 11] @@ -585,7 +585,7 @@ def test_basic_funnel_correlation_with_properties(self): ) result, _ = self._get_events_for_filters( - filters, funnelCorrelationType=FunnelCorrelationResultsType.properties, funnelCorrelationNames=["$browser"] + filters, funnelCorrelationType=FunnelCorrelationResultsType.PROPERTIES, funnelCorrelationNames=["$browser"] ) odds_ratios = [item.pop("odds_ratio") for item in result] @@ -784,7 +784,7 @@ def test_funnel_correlation_with_properties_and_groups(self): } result, _ = self._get_events_for_filters( - filters, funnelCorrelationType=FunnelCorrelationResultsType.properties, funnelCorrelationNames=["industry"] + filters, funnelCorrelationType=FunnelCorrelationResultsType.PROPERTIES, funnelCorrelationNames=["industry"] ) odds_ratios = [item.pop("odds_ratio") for item in result] @@ -852,7 +852,7 @@ def test_funnel_correlation_with_properties_and_groups(self): # test with `$all` as property # _run property correlation with filter on all properties new_result, _ = self._get_events_for_filters( - filters, funnelCorrelationType=FunnelCorrelationResultsType.properties, funnelCorrelationNames=["$all"] + filters, funnelCorrelationType=FunnelCorrelationResultsType.PROPERTIES, funnelCorrelationNames=["$all"] ) odds_ratios = [item.pop("odds_ratio") for item in new_result] @@ -989,7 +989,7 @@ def test_funnel_correlation_with_properties_and_groups_person_on_events(self): with override_instance_config("PERSON_ON_EVENTS_ENABLED", True): result, _ = self._get_events_for_filters( filters, - funnelCorrelationType=FunnelCorrelationResultsType.properties, + funnelCorrelationType=FunnelCorrelationResultsType.PROPERTIES, funnelCorrelationNames=["industry"], ) @@ -1055,7 +1055,7 @@ def test_funnel_correlation_with_properties_and_groups_person_on_events(self): # _run property correlation with filter on all properties new_result, _ = self._get_events_for_filters( filters, - funnelCorrelationType=FunnelCorrelationResultsType.properties, + funnelCorrelationType=FunnelCorrelationResultsType.PROPERTIES, funnelCorrelationNames=["$all"], ) @@ -1193,14 +1193,14 @@ def test_correlation_with_properties_raises_validation_error(self): with self.assertRaises(ValidationError): self._get_events_for_filters( filters, - funnelCorrelationType=FunnelCorrelationResultsType.properties, + funnelCorrelationType=FunnelCorrelationResultsType.PROPERTIES, # funnelCorrelationNames=["$browser"] -- missing ) with self.assertRaises(ValidationError): self._get_events_for_filters( filters, - funnelCorrelationType=FunnelCorrelationResultsType.event_with_properties, + funnelCorrelationType=FunnelCorrelationResultsType.EVENT_WITH_PROPERTIES, # "funnelCorrelationEventNames": ["rick"] -- missing ) @@ -1306,7 +1306,7 @@ def test_correlation_with_multiple_properties(self): result, _ = self._get_events_for_filters( filters, - funnelCorrelationType=FunnelCorrelationResultsType.properties, + funnelCorrelationType=FunnelCorrelationResultsType.PROPERTIES, funnelCorrelationNames=["$browser", "$nice"], ) @@ -1377,7 +1377,7 @@ def test_correlation_with_multiple_properties(self): # _run property correlation with filter on all properties new_result, _ = self._get_events_for_filters( - filters, funnelCorrelationType=FunnelCorrelationResultsType.properties, funnelCorrelationNames=["$all"] + filters, funnelCorrelationType=FunnelCorrelationResultsType.PROPERTIES, funnelCorrelationNames=["$all"] ) odds_ratios = [item.pop("odds_ratio") for item in new_result] @@ -1396,7 +1396,7 @@ def test_correlation_with_multiple_properties(self): # search for $all but exclude $browser new_result, _ = self._get_events_for_filters( filters, - funnelCorrelationType=FunnelCorrelationResultsType.properties, + funnelCorrelationType=FunnelCorrelationResultsType.PROPERTIES, funnelCorrelationNames=["$all"], funnelCorrelationExcludeNames=["$browser"], ) @@ -1506,7 +1506,7 @@ def test_discarding_insignificant_events(self): # Discard both due to % FunnelCorrelationQueryRunner.MIN_PERSON_PERCENTAGE = 0.11 FunnelCorrelationQueryRunner.MIN_PERSON_COUNT = 25 - result, _ = self._get_events_for_filters(filters, funnelCorrelationType=FunnelCorrelationResultsType.events) + result, _ = self._get_events_for_filters(filters, funnelCorrelationType=FunnelCorrelationResultsType.EVENTS) self.assertEqual(len(result), 2) @@ -1557,7 +1557,7 @@ def test_events_within_conversion_window_for_correlation(self): timestamp="2020-01-02T14:15:00Z", # event happened outside conversion window ) - result, _ = self._get_events_for_filters(filters, funnelCorrelationType=FunnelCorrelationResultsType.events) + result, _ = self._get_events_for_filters(filters, funnelCorrelationType=FunnelCorrelationResultsType.EVENTS) odds_ratios = [item.pop("odds_ratio") for item in result] expected_odds_ratios = [4] @@ -1637,7 +1637,7 @@ def test_funnel_correlation_with_event_properties(self): result, _ = self._get_events_for_filters( filters, - funnelCorrelationType=FunnelCorrelationResultsType.event_with_properties, + funnelCorrelationType=FunnelCorrelationResultsType.EVENT_WITH_PROPERTIES, funnelCorrelationEventNames=[ "positively_related", "negatively_related", @@ -1682,7 +1682,7 @@ def test_funnel_correlation_with_event_properties(self): self._get_actors_for_event( filters, "positively_related", - [EventPropertyFilter(operator=PropertyOperator.exact, key="blah", value="value_bleh")], + [EventPropertyFilter(operator=PropertyOperator.EXACT, key="blah", value="value_bleh")], ) ), 5, @@ -1692,7 +1692,7 @@ def test_funnel_correlation_with_event_properties(self): self._get_actors_for_event( filters, "positively_related", - [EventPropertyFilter(operator=PropertyOperator.exact, key="signup_source", value="facebook")], + [EventPropertyFilter(operator=PropertyOperator.EXACT, key="signup_source", value="facebook")], ) ), 3, @@ -1702,7 +1702,7 @@ def test_funnel_correlation_with_event_properties(self): self._get_actors_for_event( filters, "positively_related", - [EventPropertyFilter(operator=PropertyOperator.exact, key="signup_source", value="facebook")], + [EventPropertyFilter(operator=PropertyOperator.EXACT, key="signup_source", value="facebook")], False, ) ), @@ -1713,7 +1713,7 @@ def test_funnel_correlation_with_event_properties(self): self._get_actors_for_event( filters, "negatively_related", - [EventPropertyFilter(operator=PropertyOperator.exact, key="signup_source", value="email")], + [EventPropertyFilter(operator=PropertyOperator.EXACT, key="signup_source", value="email")], False, ) ), @@ -1803,7 +1803,7 @@ def test_funnel_correlation_with_event_properties_and_groups(self): result, _ = self._get_events_for_filters( filters, - funnelCorrelationType=FunnelCorrelationResultsType.event_with_properties, + funnelCorrelationType=FunnelCorrelationResultsType.EVENT_WITH_PROPERTIES, funnelCorrelationEventNames=[ "positively_related", "negatively_related", @@ -1888,7 +1888,7 @@ def test_funnel_correlation_with_event_properties_exclusions(self): result, _ = self._get_events_for_filters( filters, - funnelCorrelationType=FunnelCorrelationResultsType.event_with_properties, + funnelCorrelationType=FunnelCorrelationResultsType.EVENT_WITH_PROPERTIES, funnelCorrelationEventNames=["positively_related"], funnelCorrelationEventExcludePropertyNames=["signup_source"], ) @@ -1912,7 +1912,7 @@ def test_funnel_correlation_with_event_properties_exclusions(self): self._get_actors_for_event( filters, "positively_related", - [EventPropertyFilter(operator=PropertyOperator.exact, key="blah", value="value_bleh")], + [EventPropertyFilter(operator=PropertyOperator.EXACT, key="blah", value="value_bleh")], ) ), 3, @@ -1924,7 +1924,7 @@ def test_funnel_correlation_with_event_properties_exclusions(self): self._get_actors_for_event( filters, "positively_related", - [EventPropertyFilter(operator=PropertyOperator.exact, key="signup_source", value="facebook")], + [EventPropertyFilter(operator=PropertyOperator.EXACT, key="signup_source", value="facebook")], ) ), 3, @@ -1996,7 +1996,7 @@ def test_funnel_correlation_with_event_properties_autocapture(self): result, _ = self._get_events_for_filters( filters, - funnelCorrelationType=FunnelCorrelationResultsType.event_with_properties, + funnelCorrelationType=FunnelCorrelationResultsType.EVENT_WITH_PROPERTIES, funnelCorrelationEventNames=["$autocapture"], ) diff --git a/posthog/hogql_queries/insights/funnels/test/test_funnel_correlations_persons.py b/posthog/hogql_queries/insights/funnels/test/test_funnel_correlations_persons.py index 02f65a468e4eb..a6694deed8330 100644 --- a/posthog/hogql_queries/insights/funnels/test/test_funnel_correlations_persons.py +++ b/posthog/hogql_queries/insights/funnels/test/test_funnel_correlations_persons.py @@ -39,7 +39,7 @@ def get_actors( filters: dict[str, Any], team: Team, - funnelCorrelationType: Optional[FunnelCorrelationResultsType] = FunnelCorrelationResultsType.events, + funnelCorrelationType: Optional[FunnelCorrelationResultsType] = FunnelCorrelationResultsType.EVENTS, funnelCorrelationNames=None, funnelCorrelationPersonConverted: Optional[bool] = None, funnelCorrelationPersonEntity: Optional[EventsNode] = None, @@ -50,7 +50,7 @@ def get_actors( funnel_actors_query = FunnelsActorsQuery(source=funnels_query, includeRecordings=includeRecordings) correlation_query = FunnelCorrelationQuery( source=funnel_actors_query, - funnelCorrelationType=(funnelCorrelationType or FunnelCorrelationResultsType.events), + funnelCorrelationType=(funnelCorrelationType or FunnelCorrelationResultsType.EVENTS), funnelCorrelationNames=funnelCorrelationNames, # funnelCorrelationExcludeNames=funnelCorrelationExcludeNames, # funnelCorrelationExcludeEventNames=funnelCorrelationExcludeEventNames, @@ -466,7 +466,7 @@ def test_funnel_correlation_on_properties_with_recordings(self): results = get_actors( filters, self.team, - funnelCorrelationType=FunnelCorrelationResultsType.properties, + funnelCorrelationType=FunnelCorrelationResultsType.PROPERTIES, funnelCorrelationPersonConverted=True, funnelCorrelationPropertyValues=[ { @@ -579,7 +579,7 @@ def test_strict_funnel_correlation_with_recordings(self): results = get_actors( filters, self.team, - funnelCorrelationType=FunnelCorrelationResultsType.properties, + funnelCorrelationType=FunnelCorrelationResultsType.PROPERTIES, funnelCorrelationPersonConverted=True, funnelCorrelationPropertyValues=[ { @@ -613,7 +613,7 @@ def test_strict_funnel_correlation_with_recordings(self): results = get_actors( filters, self.team, - funnelCorrelationType=FunnelCorrelationResultsType.properties, + funnelCorrelationType=FunnelCorrelationResultsType.PROPERTIES, funnelCorrelationPersonConverted=False, funnelCorrelationPropertyValues=[ { diff --git a/posthog/hogql_queries/insights/funnels/utils.py b/posthog/hogql_queries/insights/funnels/utils.py index 6da16512bdceb..d5c968a913494 100644 --- a/posthog/hogql_queries/insights/funnels/utils.py +++ b/posthog/hogql_queries/insights/funnels/utils.py @@ -12,9 +12,9 @@ def get_funnel_order_class(funnelsFilter: FunnelsFilter): FunnelUnordered, ) - if funnelsFilter.funnelOrderType == StepOrderValue.unordered: + if funnelsFilter.funnelOrderType == StepOrderValue.UNORDERED: return FunnelUnordered - elif funnelsFilter.funnelOrderType == StepOrderValue.strict: + elif funnelsFilter.funnelOrderType == StepOrderValue.STRICT: return FunnelStrict return Funnel @@ -27,12 +27,12 @@ def get_funnel_actor_class(funnelsFilter: FunnelsFilter): FunnelTrendsActors, ) - if funnelsFilter.funnelVizType == FunnelVizType.trends: + if funnelsFilter.funnelVizType == FunnelVizType.TRENDS: return FunnelTrendsActors else: - if funnelsFilter.funnelOrderType == StepOrderValue.unordered: + if funnelsFilter.funnelOrderType == StepOrderValue.UNORDERED: return FunnelUnorderedActors - elif funnelsFilter.funnelOrderType == StepOrderValue.strict: + elif funnelsFilter.funnelOrderType == StepOrderValue.STRICT: return FunnelStrictActors else: return FunnelActors diff --git a/posthog/hogql_queries/insights/paths_query_runner.py b/posthog/hogql_queries/insights/paths_query_runner.py index a0c3ba92e5e95..30185e28f8923 100644 --- a/posthog/hogql_queries/insights/paths_query_runner.py +++ b/posthog/hogql_queries/insights/paths_query_runner.py @@ -82,13 +82,13 @@ def _get_event_query(self) -> list[ast.Expr]: if not self.query.pathsFilter.includeEventTypes: return [] - if PathType.field_pageview in self.query.pathsFilter.includeEventTypes: + if PathType.FIELD_PAGEVIEW in self.query.pathsFilter.includeEventTypes: or_conditions.append(parse_expr("event = {event}", {"event": ast.Constant(value=PAGEVIEW_EVENT)})) - if PathType.field_screen in self.query.pathsFilter.includeEventTypes: + if PathType.FIELD_SCREEN in self.query.pathsFilter.includeEventTypes: or_conditions.append(parse_expr("event = {event}", {"event": ast.Constant(value=SCREEN_EVENT)})) - if PathType.custom_event in self.query.pathsFilter.includeEventTypes: + if PathType.CUSTOM_EVENT in self.query.pathsFilter.includeEventTypes: or_conditions.append(parse_expr("NOT startsWith(events.event, '$')")) if or_conditions: @@ -146,8 +146,8 @@ def handle_funnel(self) -> tuple[list, Optional[ast.Expr]]: funnelSourceFilter = funnelSource.funnelsFilter or FunnelsFilter() if funnelPathType in ( - FunnelPathType.funnel_path_after_step, - FunnelPathType.funnel_path_before_step, + FunnelPathType.FUNNEL_PATH_AFTER_STEP, + FunnelPathType.FUNNEL_PATH_BEFORE_STEP, ): funnel_fields = [ ast.Alias(alias="target_timestamp", expr=ast.Field(chain=["funnel_actors", "timestamp"])), @@ -155,15 +155,15 @@ def handle_funnel(self) -> tuple[list, Optional[ast.Expr]]: interval = funnelSourceFilter.funnelWindowInterval or 14 unit = funnelSourceFilter.funnelWindowIntervalUnit interval_unit = funnel_window_interval_unit_to_sql(unit) - operator = ">=" if funnelPathType == FunnelPathType.funnel_path_after_step else "<=" + operator = ">=" if funnelPathType == FunnelPathType.FUNNEL_PATH_AFTER_STEP else "<=" default_case = f"events.timestamp {operator} toTimeZone({{target_timestamp}}, 'UTC')" - if funnelPathType == FunnelPathType.funnel_path_after_step and funnelStep and funnelStep < 0: + if funnelPathType == FunnelPathType.FUNNEL_PATH_AFTER_STEP and funnelStep and funnelStep < 0: default_case += f" + INTERVAL {interval} {interval_unit}" event_filter = parse_expr( default_case, {"target_timestamp": ast.Field(chain=["funnel_actors", "timestamp"])} ) return funnel_fields, event_filter - elif funnelPathType == FunnelPathType.funnel_path_between_steps: + elif funnelPathType == FunnelPathType.FUNNEL_PATH_BETWEEN_STEPS: funnel_fields = [ ast.Alias(alias="min_timestamp", expr=ast.Field(chain=["funnel_actors", "min_timestamp"])), ast.Alias(alias="max_timestamp", expr=ast.Field(chain=["funnel_actors", "max_timestamp"])), @@ -210,11 +210,11 @@ def funnel_join(self) -> ast.JoinExpr: assert isinstance(actors_query_runner.source_runner, FunnelsQueryRunner) assert actors_query_runner.source_runner.context is not None actors_query_runner.source_runner.context.includeTimestamp = funnelPathType in ( - FunnelPathType.funnel_path_after_step, - FunnelPathType.funnel_path_before_step, + FunnelPathType.FUNNEL_PATH_AFTER_STEP, + FunnelPathType.FUNNEL_PATH_BEFORE_STEP, ) actors_query_runner.source_runner.context.includePrecedingTimestamp = ( - funnelPathType == FunnelPathType.funnel_path_between_steps + funnelPathType == FunnelPathType.FUNNEL_PATH_BETWEEN_STEPS ) actors_query = actors_query_runner.to_query() @@ -530,7 +530,7 @@ def get_session_threshold_clause(self) -> ast.Expr: funnelSourceFilter = self.query.funnelPathsFilter.funnelSource.funnelsFilter or FunnelsFilter() interval = 14 - interval_unit = FunnelConversionWindowTimeUnit.day + interval_unit = FunnelConversionWindowTimeUnit.DAY if funnelSourceFilter.funnelWindowInterval: interval = funnelSourceFilter.funnelWindowInterval diff --git a/posthog/hogql_queries/insights/retention_query_runner.py b/posthog/hogql_queries/insights/retention_query_runner.py index c069c475262a2..7bbd39e6c14ff 100644 --- a/posthog/hogql_queries/insights/retention_query_runner.py +++ b/posthog/hogql_queries/insights/retention_query_runner.py @@ -86,7 +86,7 @@ def filter_timestamp(self) -> ast.Expr: ) def _get_events_for_entity(self, entity: RetentionEntity) -> list[str | None]: - if entity.type == EntityType.actions and entity.id: + if entity.type == EntityType.ACTIONS and entity.id: action = Action.objects.get(pk=int(entity.id)) return action.get_step_events() return [entity.id] if isinstance(entity.id, str) else [None] @@ -131,7 +131,7 @@ def actor_query(self, breakdown_values_filter: Optional[int] = None) -> ast.Sele event_query_type = ( RetentionQueryType.TARGET_FIRST_TIME - if self.query.retentionFilter.retentionType == RetentionType.retention_first_time + if self.query.retentionFilter.retentionType == RetentionType.RETENTION_FIRST_TIME else RetentionQueryType.TARGET ) @@ -290,13 +290,15 @@ def actor_query(self, breakdown_values_filter: Optional[int] = None) -> ast.Sele select_from=ast.JoinExpr(table=ast.Field(chain=["events"])), where=ast.And(exprs=event_filters), group_by=[ast.Field(chain=["actor_id"])], - having=ast.CompareOperation( - op=ast.CompareOperationOp.Eq, - left=ast.Field(chain=["breakdown_values"]), - right=ast.Constant(value=breakdown_values_filter), - ) - if breakdown_values_filter is not None - else None, + having=( + ast.CompareOperation( + op=ast.CompareOperationOp.Eq, + left=ast.Field(chain=["breakdown_values"]), + right=ast.Constant(value=breakdown_values_filter), + ) + if breakdown_values_filter is not None + else None + ), ) if self.query.samplingFactor is not None and isinstance(self.query.samplingFactor, float): inner_query.select_from.sample = ast.SampleExpr( diff --git a/posthog/hogql_queries/insights/test/test_lifecycle_query_runner.py b/posthog/hogql_queries/insights/test/test_lifecycle_query_runner.py index 4818966fbf2ee..258f4bed96859 100644 --- a/posthog/hogql_queries/insights/test/test_lifecycle_query_runner.py +++ b/posthog/hogql_queries/insights/test/test_lifecycle_query_runner.py @@ -106,7 +106,7 @@ def test_lifecycle_query_group_0(self): date_from = "2020-01-09" date_to = "2020-01-19" - response = self._run_lifecycle_query(date_from, date_to, IntervalType.day, 0) + response = self._run_lifecycle_query(date_from, date_to, IntervalType.DAY, 0) statuses = [res["status"] for res in response.results] self.assertEqual(["new", "returning", "resurrecting", "dormant"], statuses) @@ -357,7 +357,7 @@ def test_lifecycle_query_group_1(self): date_from = "2020-01-09" date_to = "2020-01-19" - response = self._run_lifecycle_query(date_from, date_to, IntervalType.day, 1) + response = self._run_lifecycle_query(date_from, date_to, IntervalType.DAY, 1) statuses = [res["status"] for res in response.results] self.assertEqual(["new", "returning", "resurrecting", "dormant"], statuses) @@ -670,7 +670,7 @@ def test_lifecycle_query_whole_range(self): date_from = "2020-01-09" date_to = "2020-01-19" - response = self._run_lifecycle_query(date_from, date_to, IntervalType.day) + response = self._run_lifecycle_query(date_from, date_to, IntervalType.DAY) statuses = [res["status"] for res in response.results] self.assertEqual(["new", "returning", "resurrecting", "dormant"], statuses) @@ -891,7 +891,7 @@ def test_events_query_whole_range(self): date_from = "2020-01-09" date_to = "2020-01-19" - response = self._run_events_query(date_from, date_to, IntervalType.day) + response = self._run_events_query(date_from, date_to, IntervalType.DAY) self.assertEqual( { @@ -919,7 +919,7 @@ def test_events_query_partial_range(self): self._create_test_events() date_from = "2020-01-12" date_to = "2020-01-14" - response = self._run_events_query(date_from, date_to, IntervalType.day) + response = self._run_events_query(date_from, date_to, IntervalType.DAY) self.assertEqual( { @@ -959,7 +959,7 @@ def test_lifecycle_trend(self): team=self.team, query=LifecycleQuery( dateRange=InsightDateRange(date_from="2020-01-12T00:00:00Z", date_to="2020-01-19T00:00:00Z"), - interval=IntervalType.day, + interval=IntervalType.DAY, series=[EventsNode(event="$pageview")], ), ) @@ -1009,7 +1009,7 @@ def test_lifecycle_trend_all_events(self): team=self.team, query=LifecycleQuery( dateRange=InsightDateRange(date_from="2020-01-12T00:00:00Z", date_to="2020-01-19T00:00:00Z"), - interval=IntervalType.day, + interval=IntervalType.DAY, series=[EventsNode(event=None)], ), ) @@ -1073,7 +1073,7 @@ def test_lifecycle_trend_with_zero_person_ids(self): team=self.team, query=LifecycleQuery( dateRange=InsightDateRange(date_from="2020-01-12T00:00:00Z", date_to="2020-01-19T00:00:00Z"), - interval=IntervalType.day, + interval=IntervalType.DAY, series=[EventsNode(event="$pageview")], ), ) @@ -1174,9 +1174,9 @@ def test_lifecycle_trend_prop_filtering(self): team=self.team, query=LifecycleQuery( dateRange=InsightDateRange(date_from="2020-01-12T00:00:00Z", date_to="2020-01-19T00:00:00Z"), - interval=IntervalType.day, + interval=IntervalType.DAY, series=[EventsNode(event="$pageview")], - properties=[EventPropertyFilter(key="$number", value="1", operator=PropertyOperator.exact)], + properties=[EventPropertyFilter(key="$number", value="1", operator=PropertyOperator.EXACT)], ), ) .calculate() @@ -1199,11 +1199,11 @@ def test_lifecycle_trend_prop_filtering(self): team=self.team, query=LifecycleQuery( dateRange=InsightDateRange(date_from="2020-01-12T00:00:00Z", date_to="2020-01-19T00:00:00Z"), - interval=IntervalType.day, + interval=IntervalType.DAY, series=[ EventsNode( event="$pageview", - properties=[EventPropertyFilter(key="$number", value="1", operator=PropertyOperator.exact)], + properties=[EventPropertyFilter(key="$number", value="1", operator=PropertyOperator.EXACT)], ) ], ), @@ -1305,11 +1305,11 @@ def test_lifecycle_trend_person_prop_filtering(self): team=self.team, query=LifecycleQuery( dateRange=InsightDateRange(date_from="2020-01-12T00:00:00Z", date_to="2020-01-19T00:00:00Z"), - interval=IntervalType.day, + interval=IntervalType.DAY, series=[ EventsNode( event="$pageview", - properties=[PersonPropertyFilter(key="name", value="p1", operator=PropertyOperator.exact)], + properties=[PersonPropertyFilter(key="name", value="p1", operator=PropertyOperator.EXACT)], ) ], ), @@ -1374,7 +1374,7 @@ def test_lifecycle_trends_distinct_id_repeat(self): team=self.team, query=LifecycleQuery( dateRange=InsightDateRange(date_from="2020-01-12T00:00:00Z", date_to="2020-01-19T00:00:00Z"), - interval=IntervalType.day, + interval=IntervalType.DAY, series=[EventsNode(event="$pageview")], ), ) @@ -1419,7 +1419,7 @@ def test_lifecycle_trend_action(self): team=self.team, query=LifecycleQuery( dateRange=InsightDateRange(date_from="2020-01-12T00:00:00Z", date_to="2020-01-19T00:00:00Z"), - interval=IntervalType.day, + interval=IntervalType.DAY, series=[ActionsNode(id=pageview_action.pk)], ), ) @@ -1463,7 +1463,7 @@ def test_lifecycle_trend_all_time(self): team=self.team, query=LifecycleQuery( dateRange=InsightDateRange(date_from="all"), - interval=IntervalType.day, + interval=IntervalType.DAY, series=[EventsNode(event="$pageview")], ), ) @@ -1510,7 +1510,7 @@ def test_lifecycle_trend_weeks_sunday(self): team=self.team, query=LifecycleQuery( dateRange=InsightDateRange(date_from="2020-02-05T00:00:00Z", date_to="2020-03-09T00:00:00Z"), - interval=IntervalType.week, + interval=IntervalType.WEEK, series=[EventsNode(event="$pageview")], ), ) @@ -1569,7 +1569,7 @@ def test_lifecycle_trend_weeks_monday(self): team=self.team, query=LifecycleQuery( dateRange=InsightDateRange(date_from="2020-02-05T00:00:00Z", date_to="2020-03-09T00:00:00Z"), - interval=IntervalType.week, + interval=IntervalType.WEEK, series=[EventsNode(event="$pageview")], ), ) @@ -1624,7 +1624,7 @@ def test_lifecycle_trend_months(self): team=self.team, query=LifecycleQuery( dateRange=InsightDateRange(date_from="2020-02-01T00:00:00Z", date_to="2020-09-01T00:00:00Z"), - interval=IntervalType.month, + interval=IntervalType.MONTH, series=[EventsNode(event="$pageview")], ), ) @@ -1667,7 +1667,7 @@ def test_filter_test_accounts(self): team=self.team, query=LifecycleQuery( dateRange=InsightDateRange(date_from="2020-01-12T00:00:00Z", date_to="2020-01-19T00:00:00Z"), - interval=IntervalType.day, + interval=IntervalType.DAY, series=[EventsNode(event="$pageview")], filterTestAccounts=True, ), @@ -1712,7 +1712,7 @@ def test_timezones(self): team=self.team, query=LifecycleQuery( dateRange=InsightDateRange(date_from="2020-01-12", date_to="2020-01-19"), - interval=IntervalType.day, + interval=IntervalType.DAY, series=[EventsNode(event="$pageview")], ), ) @@ -1738,7 +1738,7 @@ def test_timezones(self): team=self.team, query=LifecycleQuery( dateRange=InsightDateRange(date_from="2020-01-12", date_to="2020-01-19"), - interval=IntervalType.day, + interval=IntervalType.DAY, series=[EventsNode(event="$pageview")], ), ) @@ -1785,7 +1785,7 @@ def test_sampling(self): team=self.team, query=LifecycleQuery( dateRange=InsightDateRange(date_from="2020-01-12T00:00:00Z", date_to="2020-01-19T00:00:00Z"), - interval=IntervalType.day, + interval=IntervalType.DAY, series=[EventsNode(event="$pageview")], samplingFactor=0.1, ), @@ -1832,7 +1832,7 @@ def test_cohort_filter(self): team=self.team, query=LifecycleQuery( dateRange=InsightDateRange(date_from="2020-01-12T00:00:00Z", date_to="2020-01-19T00:00:00Z"), - interval=IntervalType.day, + interval=IntervalType.DAY, series=[EventsNode(event="$pageview")], properties=[CohortPropertyFilter(value=cohort.pk)], ), diff --git a/posthog/hogql_queries/insights/test/test_paginators.py b/posthog/hogql_queries/insights/test/test_paginators.py index d76d6ff2fcf01..06108117bd95a 100644 --- a/posthog/hogql_queries/insights/test/test_paginators.py +++ b/posthog/hogql_queries/insights/test/test_paginators.py @@ -112,7 +112,7 @@ def test_empty_result_set(self): select=["properties.email"], limit=10, properties=[ - PersonPropertyFilter(key="email", value="random", operator=PropertyOperator.exact), + PersonPropertyFilter(key="email", value="random", operator=PropertyOperator.EXACT), ], ) ) diff --git a/posthog/hogql_queries/insights/test/test_stickiness_query_runner.py b/posthog/hogql_queries/insights/test/test_stickiness_query_runner.py index e9dace1280091..f9a2cbc17a038 100644 --- a/posthog/hogql_queries/insights/test/test_stickiness_query_runner.py +++ b/posthog/hogql_queries/insights/test/test_stickiness_query_runner.py @@ -27,7 +27,7 @@ PersonPropertyFilter, PropertyGroupFilter, PropertyOperator, - RecordingDurationFilter, + RecordingPropertyFilter, SessionPropertyFilter, StickinessFilter, StickinessQuery, @@ -58,7 +58,7 @@ class SeriesTestData: ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -205,7 +205,7 @@ def _run_query( query_series: list[EventsNode | ActionsNode] = [EventsNode(event="$pageview")] if series is None else series query_date_from = date_from or self.default_date_from query_date_to = None if date_to == "now" else date_to or self.default_date_to - query_interval = interval or IntervalType.day + query_interval = interval or IntervalType.DAY query = StickinessQuery( series=query_series, @@ -276,7 +276,7 @@ def test_labels(self): def test_interval_hour(self): self._create_test_events() - response = self._run_query(interval=IntervalType.hour, date_from="2020-01-11", date_to="2020-01-12") + response = self._run_query(interval=IntervalType.HOUR, date_from="2020-01-11", date_to="2020-01-12") result = response.results[0] @@ -293,7 +293,7 @@ def test_interval_hour_last_days(self): self._create_test_events() with freeze_time("2020-01-20T12:00:00Z"): - response = self._run_query(interval=IntervalType.hour, date_from="-2d", date_to="now") + response = self._run_query(interval=IntervalType.HOUR, date_from="-2d", date_to="now") result = response.results[0] # 61 = 48 + 12 + 1 hours_labels = [f"{hour + 1} hour{'' if hour == 0 else 's'}" for hour in range(61)] @@ -309,7 +309,7 @@ def test_interval_hour_last_days(self): def test_interval_day(self): self._create_test_events() - response = self._run_query(interval=IntervalType.day) + response = self._run_query(interval=IntervalType.DAY) result = response.results[0] @@ -343,7 +343,7 @@ def test_interval_day(self): def test_interval_week(self): self._create_test_events() - response = self._run_query(interval=IntervalType.week) + response = self._run_query(interval=IntervalType.WEEK) result = response.results[0] @@ -356,7 +356,7 @@ def test_interval_full_weeks(self): self._create_test_events() with freeze_time("2020-01-23T12:00:00Z"): - response = self._run_query(interval=IntervalType.week, date_from="-30d", date_to="now") + response = self._run_query(interval=IntervalType.WEEK, date_from="-30d", date_to="now") result = response.results[0] @@ -368,7 +368,7 @@ def test_interval_full_weeks(self): def test_interval_month(self): self._create_test_events() - response = self._run_query(interval=IntervalType.month) + response = self._run_query(interval=IntervalType.MONTH) result = response.results[0] @@ -381,7 +381,7 @@ def test_property_filtering(self): self._create_test_events() response = self._run_query( - properties=[EventPropertyFilter(key="$browser", operator=PropertyOperator.exact, value="Chrome")] + properties=[EventPropertyFilter(key="$browser", operator=PropertyOperator.EXACT, value="Chrome")] ) result = response.results[0] @@ -425,7 +425,7 @@ def test_event_filtering(self): series: list[EventsNode | ActionsNode] = [ EventsNode( event="$pageview", - properties=[EventPropertyFilter(key="$browser", operator=PropertyOperator.exact, value="Chrome")], + properties=[EventPropertyFilter(key="$browser", operator=PropertyOperator.EXACT, value="Chrome")], ) ] @@ -545,7 +545,7 @@ def test_group_aggregations(self): self._create_test_events() series: list[EventsNode | ActionsNode] = [ - EventsNode(event="$pageview", math="unique_group", math_group_type_index=MathGroupTypeIndex.number_0) + EventsNode(event="$pageview", math="unique_group", math_group_type_index=MathGroupTypeIndex.NUMBER_0) ] response = self._run_query(series=series) diff --git a/posthog/hogql_queries/insights/trends/aggregation_operations.py b/posthog/hogql_queries/insights/trends/aggregation_operations.py index f339faf823865..075599cb9beec 100644 --- a/posthog/hogql_queries/insights/trends/aggregation_operations.py +++ b/posthog/hogql_queries/insights/trends/aggregation_operations.py @@ -326,8 +326,8 @@ def _events_query( ) -> ast.SelectQuery | ast.SelectUnionQuery: date_from_with_lookback = "{date_from} - {inclusive_lookback}" if self.chart_display_type in NON_TIME_SERIES_DISPLAY_TYPES and self.series.math in ( - BaseMathType.weekly_active, - BaseMathType.monthly_active, + BaseMathType.WEEKLY_ACTIVE, + BaseMathType.MONTHLY_ACTIVE, ): # TRICKY: On total value (non-time-series) insights, WAU/MAU math is simply meaningless. # There's no intuitive way to define the semantics of such a combination, so what we do is just turn it diff --git a/posthog/hogql_queries/insights/trends/breakdown.py b/posthog/hogql_queries/insights/trends/breakdown.py index b62a157bfc24d..49491429cf54f 100644 --- a/posthog/hogql_queries/insights/trends/breakdown.py +++ b/posthog/hogql_queries/insights/trends/breakdown.py @@ -81,7 +81,7 @@ def column_expr(self) -> ast.Alias: return ast.Alias(alias="breakdown_value", expr=self._get_breakdown_histogram_multi_if()) if self.query.breakdownFilter.breakdown_type == "cohort": - if self.modifiers.inCohortVia == InCohortVia.leftjoin_conjoined: + if self.modifiers.inCohortVia == InCohortVia.LEFTJOIN_CONJOINED: return ast.Alias( alias="breakdown_value", expr=hogql_to_string(ast.Field(chain=["__in_cohort", "cohort_id"])), diff --git a/posthog/hogql_queries/insights/trends/breakdown_values.py b/posthog/hogql_queries/insights/trends/breakdown_values.py index cc04637d5e6ec..aee02dd9ccefb 100644 --- a/posthog/hogql_queries/insights/trends/breakdown_values.py +++ b/posthog/hogql_queries/insights/trends/breakdown_values.py @@ -113,7 +113,7 @@ def get_breakdown_values(self) -> list[str | int]: select_field.expr = ast.Call(name="toString", args=[select_field.expr]) - if self.chart_display_type == ChartDisplayType.WorldMap: + if self.chart_display_type == ChartDisplayType.WORLD_MAP: breakdown_limit = BREAKDOWN_VALUES_LIMIT_FOR_COUNTRIES else: breakdown_limit = int(self.breakdown_limit) diff --git a/posthog/hogql_queries/insights/trends/display.py b/posthog/hogql_queries/insights/trends/display.py index de0cd18f8e79a..4a1229e7ba9c2 100644 --- a/posthog/hogql_queries/insights/trends/display.py +++ b/posthog/hogql_queries/insights/trends/display.py @@ -10,26 +10,26 @@ def __init__(self, display_type: ChartDisplayType | None) -> None: if display_type: self.display_type = display_type else: - self.display_type = ChartDisplayType.ActionsLineGraph + self.display_type = ChartDisplayType.ACTIONS_LINE_GRAPH # No time range def is_total_value(self) -> bool: return ( - self.display_type == ChartDisplayType.BoldNumber - or self.display_type == ChartDisplayType.ActionsPie - or self.display_type == ChartDisplayType.ActionsBarValue - or self.display_type == ChartDisplayType.WorldMap - or self.display_type == ChartDisplayType.ActionsTable + self.display_type == ChartDisplayType.BOLD_NUMBER + or self.display_type == ChartDisplayType.ACTIONS_PIE + or self.display_type == ChartDisplayType.ACTIONS_BAR_VALUE + or self.display_type == ChartDisplayType.WORLD_MAP + or self.display_type == ChartDisplayType.ACTIONS_TABLE ) def wrap_inner_query(self, inner_query: ast.SelectQuery, breakdown_enabled: bool) -> ast.SelectQuery: - if self.display_type == ChartDisplayType.ActionsLineGraphCumulative: + if self.display_type == ChartDisplayType.ACTIONS_LINE_GRAPH_CUMULATIVE: return self._get_cumulative_query(inner_query, breakdown_enabled) return inner_query def should_wrap_inner_query(self) -> bool: - return self.display_type == ChartDisplayType.ActionsLineGraphCumulative + return self.display_type == ChartDisplayType.ACTIONS_LINE_GRAPH_CUMULATIVE def _build_aggregate_dates(self, dates_queries: ast.SelectUnionQuery) -> ast.Expr: return parse_select( @@ -81,7 +81,7 @@ def _get_cumulative_query(self, inner_query: ast.SelectQuery, breakdown_enabled: alias="count", expr=ast.WindowFunction( name="sum", - args=[ast.Field(chain=["count"])], + exprs=[ast.Field(chain=["count"])], over_expr=window_expr, ), ), diff --git a/posthog/hogql_queries/insights/trends/test/test_aggregation_operations.py b/posthog/hogql_queries/insights/trends/test/test_aggregation_operations.py index 6dfb40247f364..2195ca2a344f5 100644 --- a/posthog/hogql_queries/insights/trends/test/test_aggregation_operations.py +++ b/posthog/hogql_queries/insights/trends/test/test_aggregation_operations.py @@ -64,26 +64,26 @@ def test_replace_select_from(self): @pytest.mark.parametrize( "math,math_property", [ - [BaseMathType.total, None], - [BaseMathType.dau, None], - [BaseMathType.weekly_active, None], - [BaseMathType.monthly_active, None], - [BaseMathType.unique_session, None], - [PropertyMathType.avg, "$browser"], - [PropertyMathType.sum, "$browser"], - [PropertyMathType.min, "$browser"], - [PropertyMathType.max, "$browser"], - [PropertyMathType.median, "$browser"], - [PropertyMathType.p90, "$browser"], - [PropertyMathType.p95, "$browser"], - [PropertyMathType.p99, "$browser"], - [CountPerActorMathType.avg_count_per_actor, None], - [CountPerActorMathType.min_count_per_actor, None], - [CountPerActorMathType.max_count_per_actor, None], - [CountPerActorMathType.median_count_per_actor, None], - [CountPerActorMathType.p90_count_per_actor, None], - [CountPerActorMathType.p95_count_per_actor, None], - [CountPerActorMathType.p99_count_per_actor, None], + [BaseMathType.TOTAL, None], + [BaseMathType.DAU, None], + [BaseMathType.WEEKLY_ACTIVE, None], + [BaseMathType.MONTHLY_ACTIVE, None], + [BaseMathType.UNIQUE_SESSION, None], + [PropertyMathType.AVG, "$browser"], + [PropertyMathType.SUM, "$browser"], + [PropertyMathType.MIN, "$browser"], + [PropertyMathType.MAX, "$browser"], + [PropertyMathType.MEDIAN, "$browser"], + [PropertyMathType.P90, "$browser"], + [PropertyMathType.P95, "$browser"], + [PropertyMathType.P99, "$browser"], + [CountPerActorMathType.AVG_COUNT_PER_ACTOR, None], + [CountPerActorMathType.MIN_COUNT_PER_ACTOR, None], + [CountPerActorMathType.MAX_COUNT_PER_ACTOR, None], + [CountPerActorMathType.MEDIAN_COUNT_PER_ACTOR, None], + [CountPerActorMathType.P90_COUNT_PER_ACTOR, None], + [CountPerActorMathType.P95_COUNT_PER_ACTOR, None], + [CountPerActorMathType.P99_COUNT_PER_ACTOR, None], ["hogql", None], ], ) @@ -102,7 +102,7 @@ def test_all_cases_return( series = EventsNode(event="$pageview", math=math, math_property=math_property) query_date_range = QueryDateRange(date_range=None, interval=None, now=datetime.now(), team=team) - agg_ops = AggregationOperations(team, series, ChartDisplayType.ActionsLineGraph, query_date_range, False) + agg_ops = AggregationOperations(team, series, ChartDisplayType.ACTIONS_LINE_GRAPH, query_date_range, False) res = agg_ops.select_aggregation() assert isinstance(res, ast.Expr) @@ -110,26 +110,26 @@ def test_all_cases_return( @pytest.mark.parametrize( "math,result", [ - [BaseMathType.total, False], - [BaseMathType.dau, False], - [BaseMathType.weekly_active, True], - [BaseMathType.monthly_active, True], - [BaseMathType.unique_session, False], - [PropertyMathType.avg, False], - [PropertyMathType.sum, False], - [PropertyMathType.min, False], - [PropertyMathType.max, False], - [PropertyMathType.median, False], - [PropertyMathType.p90, False], - [PropertyMathType.p95, False], - [PropertyMathType.p99, False], - [CountPerActorMathType.avg_count_per_actor, True], - [CountPerActorMathType.min_count_per_actor, True], - [CountPerActorMathType.max_count_per_actor, True], - [CountPerActorMathType.median_count_per_actor, True], - [CountPerActorMathType.p90_count_per_actor, True], - [CountPerActorMathType.p95_count_per_actor, True], - [CountPerActorMathType.p99_count_per_actor, True], + [BaseMathType.TOTAL, False], + [BaseMathType.DAU, False], + [BaseMathType.WEEKLY_ACTIVE, True], + [BaseMathType.MONTHLY_ACTIVE, True], + [BaseMathType.UNIQUE_SESSION, False], + [PropertyMathType.AVG, False], + [PropertyMathType.SUM, False], + [PropertyMathType.MIN, False], + [PropertyMathType.MAX, False], + [PropertyMathType.MEDIAN, False], + [PropertyMathType.P90, False], + [PropertyMathType.P95, False], + [PropertyMathType.P99, False], + [CountPerActorMathType.AVG_COUNT_PER_ACTOR, True], + [CountPerActorMathType.MIN_COUNT_PER_ACTOR, True], + [CountPerActorMathType.MAX_COUNT_PER_ACTOR, True], + [CountPerActorMathType.MEDIAN_COUNT_PER_ACTOR, True], + [CountPerActorMathType.P90_COUNT_PER_ACTOR, True], + [CountPerActorMathType.P95_COUNT_PER_ACTOR, True], + [CountPerActorMathType.P99_COUNT_PER_ACTOR, True], ["hogql", False], ], ) @@ -147,6 +147,6 @@ def test_requiring_query_orchestration( series = EventsNode(event="$pageview", math=math) query_date_range = QueryDateRange(date_range=None, interval=None, now=datetime.now(), team=team) - agg_ops = AggregationOperations(team, series, ChartDisplayType.ActionsLineGraph, query_date_range, False) + agg_ops = AggregationOperations(team, series, ChartDisplayType.ACTIONS_LINE_GRAPH, query_date_range, False) res = agg_ops.requires_query_orchestration() assert res == result diff --git a/posthog/hogql_queries/insights/trends/test/test_trends_actors_query_builder.py b/posthog/hogql_queries/insights/trends/test/test_trends_actors_query_builder.py index 91d5c95a77f3c..ab9761f445441 100644 --- a/posthog/hogql_queries/insights/trends/test/test_trends_actors_query_builder.py +++ b/posthog/hogql_queries/insights/trends/test/test_trends_actors_query_builder.py @@ -102,7 +102,7 @@ def test_date_range_with_timezone(self): def test_date_range_hourly(self): self.team.timezone = "Europe/Berlin" - trends_query = default_query.model_copy(update={"interval": IntervalType.hour}, deep=True) + trends_query = default_query.model_copy(update={"interval": IntervalType.HOUR}, deep=True) self.assertEqual( self._get_date_where_sql(trends_query=trends_query, time_frame="2023-05-08T15:00:00"), @@ -114,12 +114,12 @@ def test_date_range_compare_previous(self): trends_query = default_query.model_copy(update={"trendsFilter": TrendsFilter(compare=True)}, deep=True) self.assertEqual( - self._get_date_where_sql(trends_query=trends_query, time_frame="2023-05-10", compare_value=Compare.current), + self._get_date_where_sql(trends_query=trends_query, time_frame="2023-05-10", compare_value=Compare.CURRENT), "greaterOrEquals(timestamp, toDateTime('2023-05-09 22:00:00.000000')), less(timestamp, toDateTime('2023-05-10 22:00:00.000000'))", ) self.assertEqual( self._get_date_where_sql( - trends_query=trends_query, time_frame="2023-05-10", compare_value=Compare.previous + trends_query=trends_query, time_frame="2023-05-10", compare_value=Compare.PREVIOUS ), "greaterOrEquals(timestamp, toDateTime('2023-05-02 22:00:00.000000')), less(timestamp, toDateTime('2023-05-03 22:00:00.000000'))", ) @@ -127,17 +127,17 @@ def test_date_range_compare_previous(self): def test_date_range_compare_previous_hourly(self): self.team.timezone = "Europe/Berlin" trends_query = default_query.model_copy( - update={"trendsFilter": TrendsFilter(compare=True), "interval": IntervalType.hour}, deep=True + update={"trendsFilter": TrendsFilter(compare=True), "interval": IntervalType.HOUR}, deep=True ) self.assertEqual( self._get_date_where_sql( - trends_query=trends_query, time_frame="2023-05-10T15:00:00", compare_value=Compare.current + trends_query=trends_query, time_frame="2023-05-10T15:00:00", compare_value=Compare.CURRENT ), "greaterOrEquals(timestamp, toDateTime('2023-05-10 13:00:00.000000')), less(timestamp, toDateTime('2023-05-10 14:00:00.000000'))", ) self.assertEqual( self._get_date_where_sql( - trends_query=trends_query, time_frame="2023-05-10T15:00:00", compare_value=Compare.previous + trends_query=trends_query, time_frame="2023-05-10T15:00:00", compare_value=Compare.PREVIOUS ), "greaterOrEquals(timestamp, toDateTime('2023-05-03 13:00:00.000000')), less(timestamp, toDateTime('2023-05-03 14:00:00.000000'))", ) @@ -145,7 +145,7 @@ def test_date_range_compare_previous_hourly(self): def test_date_range_total_value(self): self.team.timezone = "Europe/Berlin" trends_query = default_query.model_copy( - update={"trendsFilter": TrendsFilter(display=ChartDisplayType.BoldNumber)}, deep=True + update={"trendsFilter": TrendsFilter(display=ChartDisplayType.BOLD_NUMBER)}, deep=True ) with freeze_time("2022-06-15T12:00:00.000Z"): @@ -157,23 +157,23 @@ def test_date_range_total_value(self): def test_date_range_total_value_compare_previous(self): self.team.timezone = "Europe/Berlin" trends_query = default_query.model_copy( - update={"trendsFilter": TrendsFilter(display=ChartDisplayType.BoldNumber, compare=True)}, deep=True + update={"trendsFilter": TrendsFilter(display=ChartDisplayType.BOLD_NUMBER, compare=True)}, deep=True ) with freeze_time("2022-06-15T12:00:00.000Z"): self.assertEqual( - self._get_date_where_sql(trends_query=trends_query, compare_value=Compare.current), + self._get_date_where_sql(trends_query=trends_query, compare_value=Compare.CURRENT), "greaterOrEquals(timestamp, toDateTime('2022-06-07 22:00:00.000000')), lessOrEquals(timestamp, toDateTime('2022-06-15 21:59:59.999999'))", ) self.assertEqual( - self._get_date_where_sql(trends_query=trends_query, compare_value=Compare.previous), + self._get_date_where_sql(trends_query=trends_query, compare_value=Compare.PREVIOUS), "greaterOrEquals(timestamp, toDateTime('2022-05-31 22:00:00.000000')), lessOrEquals(timestamp, toDateTime('2022-06-08 21:59:59.999999'))", ) def test_date_range_weekly_active_users_math(self): self.team.timezone = "Europe/Berlin" trends_query = default_query.model_copy( - update={"series": [EventsNode(event="$pageview", math=BaseMathType.weekly_active)]}, deep=True + update={"series": [EventsNode(event="$pageview", math=BaseMathType.WEEKLY_ACTIVE)]}, deep=True ) with freeze_time("2024-05-30T12:00:00.000Z"): @@ -186,7 +186,7 @@ def test_date_range_weekly_active_users_math_compare_previous(self): self.team.timezone = "Europe/Berlin" trends_query = default_query.model_copy( update={ - "series": [EventsNode(event="$pageview", math=BaseMathType.weekly_active)], + "series": [EventsNode(event="$pageview", math=BaseMathType.WEEKLY_ACTIVE)], "trendsFilter": TrendsFilter(compare=True), }, deep=True, @@ -195,13 +195,13 @@ def test_date_range_weekly_active_users_math_compare_previous(self): with freeze_time("2024-05-30T12:00:00.000Z"): self.assertEqual( self._get_date_where_sql( - trends_query=trends_query, time_frame="2024-05-27", compare_value=Compare.current + trends_query=trends_query, time_frame="2024-05-27", compare_value=Compare.CURRENT ), "greaterOrEquals(timestamp, minus(toDateTime('2024-05-26 22:00:00.000000'), toIntervalDay(6))), less(timestamp, toDateTime('2024-05-27 22:00:00.000000'))", ) self.assertEqual( self._get_date_where_sql( - trends_query=trends_query, time_frame="2024-05-27", compare_value=Compare.previous + trends_query=trends_query, time_frame="2024-05-27", compare_value=Compare.PREVIOUS ), "greaterOrEquals(timestamp, minus(toDateTime('2024-05-19 22:00:00.000000'), toIntervalDay(6))), less(timestamp, toDateTime('2024-05-20 22:00:00.000000'))", ) @@ -210,8 +210,8 @@ def test_date_range_weekly_active_users_math_total_value(self): self.team.timezone = "Europe/Berlin" trends_query = default_query.model_copy( update={ - "series": [EventsNode(event="$pageview", math=BaseMathType.weekly_active)], - "trendsFilter": TrendsFilter(display=ChartDisplayType.BoldNumber), + "series": [EventsNode(event="$pageview", math=BaseMathType.WEEKLY_ACTIVE)], + "trendsFilter": TrendsFilter(display=ChartDisplayType.BOLD_NUMBER), }, deep=True, ) @@ -226,22 +226,22 @@ def test_date_range_weekly_active_users_math_total_value_compare_previous(self): self.team.timezone = "Europe/Berlin" trends_query = default_query.model_copy( update={ - "series": [EventsNode(event="$pageview", math=BaseMathType.weekly_active)], - "trendsFilter": TrendsFilter(compare=True, display=ChartDisplayType.BoldNumber), + "series": [EventsNode(event="$pageview", math=BaseMathType.WEEKLY_ACTIVE)], + "trendsFilter": TrendsFilter(compare=True, display=ChartDisplayType.BOLD_NUMBER), }, deep=True, ) with freeze_time("2024-05-30T12:00:00.000Z"): self.assertEqual( - self._get_date_where_sql(trends_query=trends_query, compare_value=Compare.previous), + self._get_date_where_sql(trends_query=trends_query, compare_value=Compare.PREVIOUS), "greaterOrEquals(timestamp, minus(toDateTime('2024-05-23 21:59:59.999999'), toIntervalDay(6))), lessOrEquals(timestamp, toDateTime('2024-05-23 21:59:59.999999'))", ) def test_date_range_monthly_active_users_math(self): self.team.timezone = "Europe/Berlin" trends_query = default_query.model_copy( - update={"series": [EventsNode(event="$pageview", math=BaseMathType.monthly_active)]}, deep=True + update={"series": [EventsNode(event="$pageview", math=BaseMathType.MONTHLY_ACTIVE)]}, deep=True ) with freeze_time("2024-05-30T12:00:00.000Z"): @@ -284,7 +284,7 @@ def test_date_range_explicit_monthly_active_users_math(self): self.team.timezone = "Europe/Berlin" trends_query = default_query.model_copy( update={ - "series": [EventsNode(event="$pageview", math=BaseMathType.monthly_active)], + "series": [EventsNode(event="$pageview", math=BaseMathType.MONTHLY_ACTIVE)], "dateRange": InsightDateRange( date_from="2024-05-08T14:29:13.634000Z", date_to="2024-05-08T14:32:57.692000Z", explicitDate=True ), diff --git a/posthog/hogql_queries/insights/trends/test/test_trends_dashboard_filters.py b/posthog/hogql_queries/insights/trends/test/test_trends_dashboard_filters.py index b14e5fad7bd4c..db0879c188fac 100644 --- a/posthog/hogql_queries/insights/trends/test/test_trends_dashboard_filters.py +++ b/posthog/hogql_queries/insights/trends/test/test_trends_dashboard_filters.py @@ -52,7 +52,7 @@ def test_empty_dashboard_filters_change_nothing(self): query_runner = self._create_query_runner( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, None, ) @@ -75,7 +75,7 @@ def test_date_from_override_updates_whole_date_range(self): query_runner = self._create_query_runner( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, None, ) @@ -98,7 +98,7 @@ def test_date_from_and_date_to_override_updates_whole_date_range(self): query_runner = self._create_query_runner( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, None, ) @@ -121,7 +121,7 @@ def test_properties_set_when_no_filters_present(self): query_runner = self._create_query_runner( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, None, ) @@ -146,7 +146,7 @@ def test_properties_list_extends_filters_list(self): query_runner = self._create_query_runner( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, None, properties=[EventPropertyFilter(key="abc", value="foo", operator="exact")], ) @@ -165,16 +165,16 @@ def test_properties_list_extends_filters_list(self): assert query_runner.query.dateRange.date_from == "2020-01-09" assert query_runner.query.dateRange.date_to == "2020-01-20" assert query_runner.query.properties == PropertyGroupFilter( - type=FilterLogicalOperator.AND, + type=FilterLogicalOperator.AND_, values=[ PropertyGroupFilterValue( - type=FilterLogicalOperator.AND, + type=FilterLogicalOperator.AND_, values=[ EventPropertyFilter(key="abc", value="foo", operator="exact"), ], ), PropertyGroupFilterValue( - type=FilterLogicalOperator.AND, + type=FilterLogicalOperator.AND_, values=[ EventPropertyFilter(key="xyz", value="bar", operator="regex"), ], @@ -188,17 +188,17 @@ def test_properties_list_extends_filters_group(self): query_runner = self._create_query_runner( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, None, properties=PropertyGroupFilter( - type=FilterLogicalOperator.OR, + type=FilterLogicalOperator.OR_, values=[ PropertyGroupFilterValue( - type=FilterLogicalOperator.OR, + type=FilterLogicalOperator.OR_, values=[EventPropertyFilter(key="abc", value="foo", operator="exact")], ), PropertyGroupFilterValue( - type=FilterLogicalOperator.AND, + type=FilterLogicalOperator.AND_, values=[EventPropertyFilter(key="klm", value="foo", operator="exact")], ), ], @@ -209,14 +209,14 @@ def test_properties_list_extends_filters_group(self): assert query_runner.query.dateRange.date_from == "2020-01-09" assert query_runner.query.dateRange.date_to == "2020-01-20" assert query_runner.query.properties == PropertyGroupFilter( - type=FilterLogicalOperator.OR, + type=FilterLogicalOperator.OR_, values=[ PropertyGroupFilterValue( - type=FilterLogicalOperator.OR, + type=FilterLogicalOperator.OR_, values=[EventPropertyFilter(key="abc", value="foo", operator="exact")], ), PropertyGroupFilterValue( - type=FilterLogicalOperator.AND, + type=FilterLogicalOperator.AND_, values=[EventPropertyFilter(key="klm", value="foo", operator="exact")], ), ], @@ -235,23 +235,23 @@ def test_properties_list_extends_filters_group(self): assert query_runner.query.dateRange.date_from == "2020-01-09" assert query_runner.query.dateRange.date_to == "2020-01-20" assert query_runner.query.properties == PropertyGroupFilter( - type=FilterLogicalOperator.AND, + type=FilterLogicalOperator.AND_, values=[ PropertyGroupFilterValue( - type=FilterLogicalOperator.OR, + type=FilterLogicalOperator.OR_, values=[ PropertyGroupFilterValue( - type=FilterLogicalOperator.OR, + type=FilterLogicalOperator.OR_, values=[EventPropertyFilter(key="abc", value="foo", operator="exact")], ), PropertyGroupFilterValue( - type=FilterLogicalOperator.AND, + type=FilterLogicalOperator.AND_, values=[EventPropertyFilter(key="klm", value="foo", operator="exact")], ), ], ), PropertyGroupFilterValue( - type=FilterLogicalOperator.AND, + type=FilterLogicalOperator.AND_, values=[EventPropertyFilter(key="xyz", value="bar", operator="regex")], ), ], @@ -263,7 +263,7 @@ def test_breakdown_limit_is_removed_when_too_large_for_dashboard(self): query_runner = self._create_query_runner( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, None, breakdown=BreakdownFilter(breakdown="abc", breakdown_limit=5), ) @@ -296,7 +296,7 @@ def test_compare_is_removed_for_all_time_range(self): query_runner = self._create_query_runner( "2024-07-07", "2024-07-14", - IntervalType.day, + IntervalType.DAY, None, trends_filters=TrendsFilter(compare=True), ) diff --git a/posthog/hogql_queries/insights/trends/test/test_trends_data_warehouse_query.py b/posthog/hogql_queries/insights/trends/test/test_trends_data_warehouse_query.py index a606d560cf130..e05045c28ff51 100644 --- a/posthog/hogql_queries/insights/trends/test/test_trends_data_warehouse_query.py +++ b/posthog/hogql_queries/insights/trends/test/test_trends_data_warehouse_query.py @@ -241,7 +241,7 @@ def test_trends_breakdown(self): timestamp_field="created", ) ], - breakdownFilter=BreakdownFilter(breakdown_type=BreakdownType.data_warehouse, breakdown="prop_1"), + breakdownFilter=BreakdownFilter(breakdown_type=BreakdownType.DATA_WAREHOUSE, breakdown="prop_1"), ) with freeze_time("2023-01-07"): @@ -279,7 +279,7 @@ def test_trends_breakdown_with_property(self): ) ], properties=clean_entity_properties([{"key": "prop_1", "value": "a", "type": "data_warehouse"}]), - breakdownFilter=BreakdownFilter(breakdown_type=BreakdownType.data_warehouse, breakdown="prop_1"), + breakdownFilter=BreakdownFilter(breakdown_type=BreakdownType.DATA_WAREHOUSE, breakdown="prop_1"), ) with freeze_time("2023-01-07"): @@ -317,11 +317,11 @@ def assert_column_names_with_display_type(self, display_type: ChartDisplayType): assert set(response.columns).issubset({"date", "total"}) def test_column_names_with_display_type(self): - self.assert_column_names_with_display_type(ChartDisplayType.ActionsAreaGraph) - self.assert_column_names_with_display_type(ChartDisplayType.ActionsBar) - self.assert_column_names_with_display_type(ChartDisplayType.ActionsBarValue) - self.assert_column_names_with_display_type(ChartDisplayType.ActionsLineGraph) - self.assert_column_names_with_display_type(ChartDisplayType.ActionsPie) - self.assert_column_names_with_display_type(ChartDisplayType.BoldNumber) - self.assert_column_names_with_display_type(ChartDisplayType.WorldMap) - self.assert_column_names_with_display_type(ChartDisplayType.ActionsLineGraphCumulative) + self.assert_column_names_with_display_type(ChartDisplayType.ACTIONS_AREA_GRAPH) + self.assert_column_names_with_display_type(ChartDisplayType.ACTIONS_BAR) + self.assert_column_names_with_display_type(ChartDisplayType.ACTIONS_BAR_VALUE) + self.assert_column_names_with_display_type(ChartDisplayType.ACTIONS_LINE_GRAPH) + self.assert_column_names_with_display_type(ChartDisplayType.ACTIONS_PIE) + self.assert_column_names_with_display_type(ChartDisplayType.BOLD_NUMBER) + self.assert_column_names_with_display_type(ChartDisplayType.WORLD_MAP) + self.assert_column_names_with_display_type(ChartDisplayType.ACTIONS_LINE_GRAPH_CUMULATIVE) diff --git a/posthog/hogql_queries/insights/trends/test/test_trends_persons.py b/posthog/hogql_queries/insights/trends/test/test_trends_persons.py index 27ac8f3b2da6b..34cde171dfa4f 100644 --- a/posthog/hogql_queries/insights/trends/test/test_trends_persons.py +++ b/posthog/hogql_queries/insights/trends/test/test_trends_persons.py @@ -277,7 +277,7 @@ def test_trends_person_breakdown_persons(self): source_query = TrendsQuery( series=[EventsNode(event="$pageview")], dateRange=InsightDateRange(date_from="-7d"), - breakdownFilter=BreakdownFilter(breakdown="$geoip_country_code", breakdown_type=BreakdownType.person), + breakdownFilter=BreakdownFilter(breakdown="$geoip_country_code", breakdown_type=BreakdownType.PERSON), ) result = self._get_actors(trends_query=source_query, day="2023-05-01", breakdown="DE") @@ -338,7 +338,7 @@ def test_trends_breakdown_hogql_persons(self): source_query = TrendsQuery( series=[EventsNode(event="$pageview")], dateRange=InsightDateRange(date_from="-7d"), - breakdownFilter=BreakdownFilter(breakdown="properties.some_property", breakdown_type=BreakdownType.hogql), + breakdownFilter=BreakdownFilter(breakdown="properties.some_property", breakdown_type=BreakdownType.HOGQL), ) result = self._get_actors(trends_query=source_query, day="2023-05-01", breakdown=20) @@ -361,7 +361,7 @@ def test_trends_cohort_breakdown_persons(self): source_query = TrendsQuery( series=[EventsNode(event="$pageview")], dateRange=InsightDateRange(date_from="-7d"), - breakdownFilter=BreakdownFilter(breakdown=[cohort.pk], breakdown_type=BreakdownType.cohort), + breakdownFilter=BreakdownFilter(breakdown=[cohort.pk], breakdown_type=BreakdownType.COHORT), ) result = self._get_actors(trends_query=source_query, day="2023-05-01", breakdown=cohort.pk) @@ -387,7 +387,7 @@ def test_trends_multi_cohort_breakdown_persons(self): source_query = TrendsQuery( series=[EventsNode(event="$pageview")], dateRange=InsightDateRange(date_from="-7d"), - breakdownFilter=BreakdownFilter(breakdown=[cohort1.pk, cohort2.pk], breakdown_type=BreakdownType.cohort), + breakdownFilter=BreakdownFilter(breakdown=[cohort1.pk, cohort2.pk], breakdown_type=BreakdownType.COHORT), ) result = self._get_actors(trends_query=source_query, day="2023-05-01", breakdown=cohort1.pk) @@ -414,7 +414,7 @@ def trends_all_cohort_breakdown_persons(self, inCohortVia: str): source_query = TrendsQuery( series=[EventsNode(event="$pageview")], dateRange=InsightDateRange(date_from="-7d"), - breakdownFilter=BreakdownFilter(breakdown=[cohort1.pk, "all"], breakdown_type=BreakdownType.cohort), + breakdownFilter=BreakdownFilter(breakdown=[cohort1.pk, "all"], breakdown_type=BreakdownType.COHORT), ) source_query.modifiers = HogQLQueryModifiers(inCohortVia=inCohortVia) @@ -458,7 +458,7 @@ def test_trends_math_weekly_active_persons(self): team=self.team, ) source_query = TrendsQuery( - series=[EventsNode(event="$pageview", math=BaseMathType.weekly_active)], + series=[EventsNode(event="$pageview", math=BaseMathType.WEEKLY_ACTIVE)], dateRange=InsightDateRange(date_from="-7d"), ) @@ -473,7 +473,7 @@ def test_trends_math_weekly_active_persons(self): def test_trends_math_property_sum_persons(self): self._create_events() source_query = TrendsQuery( - series=[EventsNode(event="$pageview", math=PropertyMathType.sum, math_property="some_property")], + series=[EventsNode(event="$pageview", math=PropertyMathType.SUM, math_property="some_property")], dateRange=InsightDateRange(date_from="-7d"), ) @@ -493,7 +493,7 @@ def test_trends_math_count_per_actor_persons(self): source_query = TrendsQuery( series=[ EventsNode( - event="$pageview", math=CountPerActorMathType.max_count_per_actor, math_property="some_property" + event="$pageview", math=CountPerActorMathType.MAX_COUNT_PER_ACTOR, math_property="some_property" ) ], dateRange=InsightDateRange(date_from="-7d"), @@ -537,7 +537,7 @@ def test_trends_math_group_persons(self): ) source_query = TrendsQuery( series=[ - EventsNode(event="$pageview", math="unique_group", math_group_type_index=MathGroupTypeIndex.number_0) + EventsNode(event="$pageview", math="unique_group", math_group_type_index=MathGroupTypeIndex.NUMBER_0) ], dateRange=InsightDateRange(date_from="-7d"), ) @@ -570,7 +570,7 @@ def test_trends_math_group_persons_filters_empty(self): ) source_query = TrendsQuery( series=[ - EventsNode(event="$pageview", math="unique_group", math_group_type_index=MathGroupTypeIndex.number_0) + EventsNode(event="$pageview", math="unique_group", math_group_type_index=MathGroupTypeIndex.NUMBER_0) ], dateRange=InsightDateRange(date_from="-7d"), ) @@ -586,7 +586,7 @@ def test_trends_total_value_persons(self): source_query = TrendsQuery( series=[EventsNode(event="$pageview")], dateRange=InsightDateRange(date_from="-7d"), - trendsFilter=TrendsFilter(display=ChartDisplayType.BoldNumber), + trendsFilter=TrendsFilter(display=ChartDisplayType.BOLD_NUMBER), ) with freeze_time("2023-05-01T20:00:00.000Z"): @@ -607,13 +607,13 @@ def test_trends_compare_persons(self): trendsFilter=TrendsFilter(compare=True), ) - result = self._get_actors(trends_query=source_query, day="2023-05-06", compare=Compare.current) + result = self._get_actors(trends_query=source_query, day="2023-05-06", compare=Compare.CURRENT) self.assertEqual(len(result), 1) self.assertEqual(get_distinct_id(result[0]), "person1") self.assertEqual(get_event_count(result[0]), 1) - result = self._get_actors(trends_query=source_query, day="2023-05-06", compare=Compare.previous) + result = self._get_actors(trends_query=source_query, day="2023-05-06", compare=Compare.PREVIOUS) self.assertEqual(len(result), 2) self.assertEqual(get_distinct_id(result[0]), "person2") diff --git a/posthog/hogql_queries/insights/trends/test/test_trends_query_builder.py b/posthog/hogql_queries/insights/trends/test/test_trends_query_builder.py index 3b9b69fa289c2..4826813cb54de 100644 --- a/posthog/hogql_queries/insights/trends/test/test_trends_query_builder.py +++ b/posthog/hogql_queries/insights/trends/test/test_trends_query_builder.py @@ -74,7 +74,7 @@ def test_column_names(self): trends_query = TrendsQuery( kind="TrendsQuery", dateRange=InsightDateRange(date_from="2023-01-01"), - series=[EventsNode(event="$pageview", math=BaseMathType.total)], + series=[EventsNode(event="$pageview", math=BaseMathType.TOTAL)], ) response = self.get_response(trends_query) @@ -101,7 +101,7 @@ def assert_column_names_with_display_type_and_breakdowns(self, display_type: Cha dateRange=InsightDateRange(date_from="2023-01-01"), series=[EventsNode(event="$pageview")], trendsFilter=TrendsFilter(display=display_type), - breakdownFilter=BreakdownFilter(breakdown="$geoip_country_code", breakdown_type=BreakdownType.event), + breakdownFilter=BreakdownFilter(breakdown="$geoip_country_code", breakdown_type=BreakdownType.EVENT), ) response = self.get_response(trends_query) @@ -110,20 +110,20 @@ def assert_column_names_with_display_type_and_breakdowns(self, display_type: Cha assert set(response.columns).issubset({"date", "total", "breakdown_value"}) def test_column_names_with_display_type(self): - self.assert_column_names_with_display_type(ChartDisplayType.ActionsAreaGraph) - self.assert_column_names_with_display_type(ChartDisplayType.ActionsBar) - self.assert_column_names_with_display_type(ChartDisplayType.ActionsBarValue) - self.assert_column_names_with_display_type(ChartDisplayType.ActionsLineGraph) - self.assert_column_names_with_display_type(ChartDisplayType.ActionsPie) - self.assert_column_names_with_display_type(ChartDisplayType.BoldNumber) - self.assert_column_names_with_display_type(ChartDisplayType.WorldMap) - self.assert_column_names_with_display_type(ChartDisplayType.ActionsLineGraphCumulative) + self.assert_column_names_with_display_type(ChartDisplayType.ACTIONS_AREA_GRAPH) + self.assert_column_names_with_display_type(ChartDisplayType.ACTIONS_BAR) + self.assert_column_names_with_display_type(ChartDisplayType.ACTIONS_BAR_VALUE) + self.assert_column_names_with_display_type(ChartDisplayType.ACTIONS_LINE_GRAPH) + self.assert_column_names_with_display_type(ChartDisplayType.ACTIONS_PIE) + self.assert_column_names_with_display_type(ChartDisplayType.BOLD_NUMBER) + self.assert_column_names_with_display_type(ChartDisplayType.WORLD_MAP) + self.assert_column_names_with_display_type(ChartDisplayType.ACTIONS_LINE_GRAPH_CUMULATIVE) def test_column_names_with_display_type_and_breakdowns(self): - self.assert_column_names_with_display_type_and_breakdowns(ChartDisplayType.ActionsAreaGraph) - self.assert_column_names_with_display_type_and_breakdowns(ChartDisplayType.ActionsBar) - self.assert_column_names_with_display_type_and_breakdowns(ChartDisplayType.ActionsBarValue) - self.assert_column_names_with_display_type_and_breakdowns(ChartDisplayType.ActionsLineGraph) - self.assert_column_names_with_display_type_and_breakdowns(ChartDisplayType.ActionsPie) - self.assert_column_names_with_display_type_and_breakdowns(ChartDisplayType.WorldMap) - self.assert_column_names_with_display_type_and_breakdowns(ChartDisplayType.ActionsLineGraphCumulative) + self.assert_column_names_with_display_type_and_breakdowns(ChartDisplayType.ACTIONS_AREA_GRAPH) + self.assert_column_names_with_display_type_and_breakdowns(ChartDisplayType.ACTIONS_BAR) + self.assert_column_names_with_display_type_and_breakdowns(ChartDisplayType.ACTIONS_BAR_VALUE) + self.assert_column_names_with_display_type_and_breakdowns(ChartDisplayType.ACTIONS_LINE_GRAPH) + self.assert_column_names_with_display_type_and_breakdowns(ChartDisplayType.ACTIONS_PIE) + self.assert_column_names_with_display_type_and_breakdowns(ChartDisplayType.WORLD_MAP) + self.assert_column_names_with_display_type_and_breakdowns(ChartDisplayType.ACTIONS_LINE_GRAPH_CUMULATIVE) diff --git a/posthog/hogql_queries/insights/trends/test/test_trends_query_runner.py b/posthog/hogql_queries/insights/trends/test/test_trends_query_runner.py index d3f92ba85c253..98a75f49d4a6a 100644 --- a/posthog/hogql_queries/insights/trends/test/test_trends_query_runner.py +++ b/posthog/hogql_queries/insights/trends/test/test_trends_query_runner.py @@ -224,7 +224,7 @@ def test_trends_label(self): response = self._run_trends_query( self.default_date_from, self.default_date_to, - IntervalType.day, + IntervalType.DAY, None, None, None, @@ -238,7 +238,7 @@ def test_trends_count(self): response = self._run_trends_query( self.default_date_from, self.default_date_to, - IntervalType.day, + IntervalType.DAY, None, None, None, @@ -252,7 +252,7 @@ def test_trends_data(self): response = self._run_trends_query( self.default_date_from, self.default_date_to, - IntervalType.day, + IntervalType.DAY, None, None, None, @@ -266,7 +266,7 @@ def test_trends_days(self): response = self._run_trends_query( self.default_date_from, self.default_date_to, - IntervalType.day, + IntervalType.DAY, None, None, None, @@ -295,7 +295,7 @@ def test_trends_labels(self): response = self._run_trends_query( self.default_date_from, self.default_date_to, - IntervalType.day, + IntervalType.DAY, None, None, None, @@ -324,7 +324,7 @@ def test_trends_labels_hour(self): response = self._run_trends_query( self.default_date_from, self.default_date_from, - IntervalType.hour, + IntervalType.HOUR, [EventsNode(event="$pageview")], ) @@ -342,7 +342,7 @@ def test_trends_multiple_series(self): response = self._run_trends_query( self.default_date_from, self.default_date_to, - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview"), EventsNode(event="$pageleave")], ) @@ -363,7 +363,7 @@ def test_formula(self): response = self._run_trends_query( self.default_date_from, self.default_date_to, - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview"), EventsNode(event="$pageleave")], TrendsFilter(formula="A+2*B"), ) @@ -379,11 +379,11 @@ def test_formula_total_value(self): response = self._run_trends_query( self.default_date_from, self.default_date_to, - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview"), EventsNode(event="$pageleave")], TrendsFilter( formula="A+2*B", - display=ChartDisplayType.BoldNumber, # total value + display=ChartDisplayType.BOLD_NUMBER, # total value ), ) self.assertEqual(1, len(response.results)) @@ -398,7 +398,7 @@ def test_formula_with_compare(self): response = self._run_trends_query( "2020-01-15", "2020-01-19", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview"), EventsNode(event="$pageleave")], TrendsFilter(formula="A+2*B", compare=True), ) @@ -426,11 +426,11 @@ def test_formula_with_compare_total_value(self): response = self._run_trends_query( "2020-01-15", "2020-01-19", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview"), EventsNode(event="$pageleave")], TrendsFilter( formula="A+2*B", - display=ChartDisplayType.BoldNumber, # total value + display=ChartDisplayType.BOLD_NUMBER, # total value compare=True, ), ) @@ -457,10 +457,10 @@ def test_formula_with_breakdown(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview"), EventsNode(event="$pageleave")], TrendsFilter(formula="A+2*B"), - BreakdownFilter(breakdown_type=BreakdownType.event, breakdown="$browser"), + BreakdownFilter(breakdown_type=BreakdownType.EVENT, breakdown="$browser"), ) # one for each breakdown value @@ -485,10 +485,10 @@ def test_formula_with_breakdown_and_compare(self): response = self._run_trends_query( "2020-01-15", "2020-01-19", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview"), EventsNode(event="$pageleave")], TrendsFilter(formula="A+2*B", compare=True), - BreakdownFilter(breakdown_type=BreakdownType.event, breakdown="$browser"), + BreakdownFilter(breakdown_type=BreakdownType.EVENT, breakdown="$browser"), ) # chrome, ff and edge for previous, and chrome and safari for current @@ -515,14 +515,14 @@ def test_formula_with_breakdown_and_compare_total_value(self): response = self._run_trends_query( "2020-01-15", "2020-01-19", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview"), EventsNode(event="$pageleave")], TrendsFilter( formula="A+2*B", - display=ChartDisplayType.BoldNumber, # total value + display=ChartDisplayType.BOLD_NUMBER, # total value compare=True, ), - BreakdownFilter(breakdown_type=BreakdownType.event, breakdown="$browser"), + BreakdownFilter(breakdown_type=BreakdownType.EVENT, breakdown="$browser"), ) # chrome, ff and edge for previous, and chrome and safari for current @@ -582,10 +582,10 @@ def test_formula_with_multi_cohort_breakdown(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview"), EventsNode(event="$pageleave")], TrendsFilter(formula="A+B"), - BreakdownFilter(breakdown_type=BreakdownType.cohort, breakdown=[cohort1.pk, cohort2.pk]), + BreakdownFilter(breakdown_type=BreakdownType.COHORT, breakdown=[cohort1.pk, cohort2.pk]), ) assert len(response.results) == 2 @@ -624,10 +624,10 @@ def test_formula_with_multi_cohort_all_breakdown(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview"), EventsNode(event="$pageleave")], TrendsFilter(formula="A+B"), - BreakdownFilter(breakdown_type=BreakdownType.cohort, breakdown=[cohort1.pk, "all"]), + BreakdownFilter(breakdown_type=BreakdownType.COHORT, breakdown=[cohort1.pk, "all"]), ) assert len(response.results) == 2 @@ -651,10 +651,10 @@ def test_formula_with_breakdown_and_no_data(self): response = self._run_trends_query( self.default_date_from, self.default_date_to, - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageviewxxx"), EventsNode(event="$pageleavexxx")], TrendsFilter(formula="A+2*B"), - BreakdownFilter(breakdown_type=BreakdownType.person, breakdown="$browser"), + BreakdownFilter(breakdown_type=BreakdownType.PERSON, breakdown="$browser"), ) self.assertEqual(0, len(response.results)) @@ -662,10 +662,10 @@ def test_formula_with_breakdown_and_no_data(self): response = self._run_trends_query( self.default_date_from, self.default_date_to, - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview"), EventsNode(event="$pageleavexxx")], TrendsFilter(formula="A+2*B"), - BreakdownFilter(breakdown_type=BreakdownType.person, breakdown="$browser"), + BreakdownFilter(breakdown_type=BreakdownType.PERSON, breakdown="$browser"), ) self.assertEqual([1, 0, 1, 3, 1, 0, 2, 0, 1, 0, 1], response.results[0]["data"]) @@ -676,10 +676,10 @@ def test_breakdown_is_context_aware(self, mock_sync_execute: MagicMock): self._run_trends_query( self.default_date_from, self.default_date_to, - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageviewxxx"), EventsNode(event="$pageleavexxx")], TrendsFilter(formula="A+2*B"), - BreakdownFilter(breakdown_type=BreakdownType.person, breakdown="$browser"), + BreakdownFilter(breakdown_type=BreakdownType.PERSON, breakdown="$browser"), limit_context=LimitContext.QUERY_ASYNC, ) @@ -693,7 +693,7 @@ def test_trends_compare(self): response = self._run_trends_query( "2020-01-15", "2020-01-19", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], TrendsFilter(compare=True), ) @@ -737,7 +737,7 @@ def test_trends_compare_weeks(self): response = self._run_trends_query( "-7d", None, - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], TrendsFilter(compare=True), ) @@ -790,10 +790,10 @@ def test_trends_breakdowns(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], None, - BreakdownFilter(breakdown_type=BreakdownType.event, breakdown="$browser"), + BreakdownFilter(breakdown_type=BreakdownType.EVENT, breakdown="$browser"), ) breakdown_labels = [result["breakdown_value"] for result in response.results] @@ -815,10 +815,10 @@ def test_trends_breakdowns_boolean(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], None, - BreakdownFilter(breakdown_type=BreakdownType.event, breakdown="bool_field"), + BreakdownFilter(breakdown_type=BreakdownType.EVENT, breakdown="bool_field"), ) breakdown_labels = [result["breakdown_value"] for result in response.results] @@ -838,11 +838,11 @@ def test_trends_breakdowns_histogram(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], None, BreakdownFilter( - breakdown_type=BreakdownType.event, + breakdown_type=BreakdownType.EVENT, breakdown="prop", breakdown_histogram_bin_count=4, ), @@ -885,10 +885,10 @@ def test_trends_breakdowns_cohort(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], None, - BreakdownFilter(breakdown_type=BreakdownType.cohort, breakdown=[cohort.pk]), + BreakdownFilter(breakdown_type=BreakdownType.COHORT, breakdown=[cohort.pk]), ) assert len(response.results) == 1 @@ -916,10 +916,10 @@ def test_trends_breakdowns_hogql(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], None, - BreakdownFilter(breakdown_type=BreakdownType.hogql, breakdown="properties.$browser"), + BreakdownFilter(breakdown_type=BreakdownType.HOGQL, breakdown="properties.$browser"), ) breakdown_labels = [result["breakdown_value"] for result in response.results] @@ -941,10 +941,10 @@ def test_trends_breakdowns_multiple_hogql(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview"), EventsNode(event="$pageleave")], None, - BreakdownFilter(breakdown_type=BreakdownType.hogql, breakdown="properties.$browser"), + BreakdownFilter(breakdown_type=BreakdownType.HOGQL, breakdown="properties.$browser"), ) breakdown_labels = [result["breakdown_value"] for result in response.results] @@ -974,10 +974,10 @@ def test_trends_breakdowns_and_compare(self): response = self._run_trends_query( "2020-01-15", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], TrendsFilter(compare=True), - BreakdownFilter(breakdown_type=BreakdownType.event, breakdown="$browser"), + BreakdownFilter(breakdown_type=BreakdownType.EVENT, breakdown="$browser"), ) breakdown_labels = [result["breakdown_value"] for result in response.results] @@ -1021,10 +1021,10 @@ def test_trends_breakdown_and_aggregation_query_orchestration(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, - [EventsNode(event="$pageview", math=PropertyMathType.sum, math_property="prop")], + IntervalType.DAY, + [EventsNode(event="$pageview", math=PropertyMathType.SUM, math_property="prop")], None, - BreakdownFilter(breakdown_type=BreakdownType.event, breakdown="$browser"), + BreakdownFilter(breakdown_type=BreakdownType.EVENT, breakdown="$browser"), ) breakdown_labels = [result["breakdown_value"] for result in response.results] @@ -1100,7 +1100,7 @@ def test_trends_aggregation_hogql(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview", math="hogql", math_hogql="sum(properties.prop)")], None, None, @@ -1128,8 +1128,8 @@ def test_trends_aggregation_total(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, - [EventsNode(event="$pageview", math=BaseMathType.total)], + IntervalType.DAY, + [EventsNode(event="$pageview", math=BaseMathType.TOTAL)], None, None, ) @@ -1143,8 +1143,8 @@ def test_trends_aggregation_dau(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, - [EventsNode(event="$pageview", math=BaseMathType.dau)], + IntervalType.DAY, + [EventsNode(event="$pageview", math=BaseMathType.DAU)], None, None, ) @@ -1158,8 +1158,8 @@ def test_trends_aggregation_wau(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, - [EventsNode(event="$pageview", math=BaseMathType.weekly_active)], + IntervalType.DAY, + [EventsNode(event="$pageview", math=BaseMathType.WEEKLY_ACTIVE)], None, None, ) @@ -1173,8 +1173,8 @@ def test_trends_aggregation_mau(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, - [EventsNode(event="$pageview", math=BaseMathType.monthly_active)], + IntervalType.DAY, + [EventsNode(event="$pageview", math=BaseMathType.MONTHLY_ACTIVE)], None, None, ) @@ -1188,8 +1188,8 @@ def test_trends_aggregation_unique(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, - [EventsNode(event="$pageview", math=BaseMathType.unique_session)], + IntervalType.DAY, + [EventsNode(event="$pageview", math=BaseMathType.UNIQUE_SESSION)], None, None, ) @@ -1203,8 +1203,8 @@ def test_trends_aggregation_property_sum(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, - [EventsNode(event="$pageview", math=PropertyMathType.sum, math_property="prop")], + IntervalType.DAY, + [EventsNode(event="$pageview", math=PropertyMathType.SUM, math_property="prop")], None, None, ) @@ -1231,8 +1231,8 @@ def test_trends_aggregation_property_avg(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, - [EventsNode(event="$pageview", math=PropertyMathType.avg, math_property="prop")], + IntervalType.DAY, + [EventsNode(event="$pageview", math=PropertyMathType.AVG, math_property="prop")], None, None, ) @@ -1259,8 +1259,8 @@ def test_trends_aggregation_per_actor_max(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, - [EventsNode(event="$pageview", math=CountPerActorMathType.max_count_per_actor)], + IntervalType.DAY, + [EventsNode(event="$pageview", math=CountPerActorMathType.MAX_COUNT_PER_ACTOR)], None, None, ) @@ -1287,9 +1287,9 @@ def test_trends_display_aggregate(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], - TrendsFilter(display=ChartDisplayType.BoldNumber), + TrendsFilter(display=ChartDisplayType.BOLD_NUMBER), None, ) @@ -1305,9 +1305,9 @@ def test_trends_display_cumulative(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], - TrendsFilter(display=ChartDisplayType.ActionsLineGraphCumulative), + TrendsFilter(display=ChartDisplayType.ACTIONS_LINE_GRAPH_CUMULATIVE), None, ) @@ -1343,10 +1343,10 @@ def test_breakdown_values_limit(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], - TrendsFilter(display=ChartDisplayType.ActionsLineGraph), - BreakdownFilter(breakdown="breakdown_value", breakdown_type=BreakdownType.event), + TrendsFilter(display=ChartDisplayType.ACTIONS_LINE_GRAPH), + BreakdownFilter(breakdown="breakdown_value", breakdown_type=BreakdownType.EVENT), ) self.assertEqual(len(response.results), 26) @@ -1354,20 +1354,20 @@ def test_breakdown_values_limit(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], - TrendsFilter(display=ChartDisplayType.ActionsLineGraph), - BreakdownFilter(breakdown="breakdown_value", breakdown_type=BreakdownType.event, breakdown_limit=10), + TrendsFilter(display=ChartDisplayType.ACTIONS_LINE_GRAPH), + BreakdownFilter(breakdown="breakdown_value", breakdown_type=BreakdownType.EVENT, breakdown_limit=10), ) self.assertEqual(len(response.results), 11) response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], - TrendsFilter(display=ChartDisplayType.ActionsLineGraph), - BreakdownFilter(breakdown="breakdown_value", breakdown_type=BreakdownType.event), + TrendsFilter(display=ChartDisplayType.ACTIONS_LINE_GRAPH), + BreakdownFilter(breakdown="breakdown_value", breakdown_type=BreakdownType.EVENT), limit_context=LimitContext.EXPORT, ) self.assertEqual(len(response.results), 30) @@ -1386,10 +1386,10 @@ def test_breakdown_values_unknown_property(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], - TrendsFilter(display=ChartDisplayType.ActionsLineGraph), - BreakdownFilter(breakdown="breakdown_value", breakdown_type=BreakdownType.event), + TrendsFilter(display=ChartDisplayType.ACTIONS_LINE_GRAPH), + BreakdownFilter(breakdown="breakdown_value", breakdown_type=BreakdownType.EVENT), ) self.assertEqual(len(response.results), 26) @@ -1397,10 +1397,10 @@ def test_breakdown_values_unknown_property(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], - TrendsFilter(display=ChartDisplayType.ActionsLineGraph), - BreakdownFilter(breakdown="breakdown_value", breakdown_type=BreakdownType.event, breakdown_limit=10), + TrendsFilter(display=ChartDisplayType.ACTIONS_LINE_GRAPH), + BreakdownFilter(breakdown="breakdown_value", breakdown_type=BreakdownType.EVENT, breakdown_limit=10), ) self.assertEqual(len(response.results), 11) @@ -1419,10 +1419,10 @@ def test_breakdown_values_world_map_limit(self): query_runner = self._create_query_runner( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], - TrendsFilter(display=ChartDisplayType.WorldMap), - BreakdownFilter(breakdown="breakdown_value", breakdown_type=BreakdownType.event), + TrendsFilter(display=ChartDisplayType.WORLD_MAP), + BreakdownFilter(breakdown="breakdown_value", breakdown_type=BreakdownType.EVENT), ) query = query_runner.to_queries()[0] assert isinstance(query, ast.SelectQuery) and query.limit == ast.Constant(value=MAX_SELECT_RETURNED_ROWS) @@ -1436,9 +1436,9 @@ def test_previous_period_with_number_display(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], - TrendsFilter(display=ChartDisplayType.BoldNumber, compare=True), + TrendsFilter(display=ChartDisplayType.BOLD_NUMBER, compare=True), None, ) @@ -1477,7 +1477,7 @@ def test_formula_rounding(self): response = self._run_trends_query( "2020-01-11T00:00:00Z", "2020-01-11T23:59:59Z", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview"), EventsNode(event="$pageleave")], TrendsFilter(formula="B/A"), ) @@ -1508,7 +1508,7 @@ def test_properties_filtering_with_materialized_columns_and_empty_string_as_prop response = self._run_trends_query( date_from="2020-01-11T00:00:00Z", date_to="2020-01-11T23:59:59Z", - interval=IntervalType.day, + interval=IntervalType.DAY, series=[EventsNode(event="$pageview")], filter_test_accounts=True, ) @@ -1521,7 +1521,7 @@ def test_smoothing(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], TrendsFilter(smoothingIntervals=7), None, @@ -1574,14 +1574,14 @@ def test_cohort_modifier(self, patch_create_default_modifiers_for_team): self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], None, - BreakdownFilter(breakdown_type=BreakdownType.cohort, breakdown=[cohort1.pk, cohort2.pk]), + BreakdownFilter(breakdown_type=BreakdownType.COHORT, breakdown=[cohort1.pk, cohort2.pk]), hogql_modifiers=modifiers, ) - assert modifiers.inCohortVia == InCohortVia.leftjoin_conjoined + assert modifiers.inCohortVia == InCohortVia.LEFTJOIN_CONJOINED @patch("posthog.hogql_queries.query_runner.create_default_modifiers_for_team") def test_cohort_modifier_with_all_cohort(self, patch_create_default_modifiers_for_team): @@ -1628,14 +1628,14 @@ def test_cohort_modifier_with_all_cohort(self, patch_create_default_modifiers_fo self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], None, - BreakdownFilter(breakdown_type=BreakdownType.cohort, breakdown=[cohort1.pk, cohort2.pk, "all"]), + BreakdownFilter(breakdown_type=BreakdownType.COHORT, breakdown=[cohort1.pk, cohort2.pk, "all"]), hogql_modifiers=modifiers, ) - assert modifiers.inCohortVia == InCohortVia.auto + assert modifiers.inCohortVia == InCohortVia.AUTO @patch("posthog.hogql_queries.query_runner.create_default_modifiers_for_team") def test_cohort_modifier_with_too_few_cohorts(self, patch_create_default_modifiers_for_team): @@ -1682,14 +1682,14 @@ def test_cohort_modifier_with_too_few_cohorts(self, patch_create_default_modifie self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], None, - BreakdownFilter(breakdown_type=BreakdownType.cohort, breakdown=[cohort1.pk, cohort2.pk, "all"]), + BreakdownFilter(breakdown_type=BreakdownType.COHORT, breakdown=[cohort1.pk, cohort2.pk, "all"]), hogql_modifiers=modifiers, ) - assert modifiers.inCohortVia == InCohortVia.auto + assert modifiers.inCohortVia == InCohortVia.AUTO @patch("posthog.hogql_queries.insights.trends.trends_query_runner.execute_hogql_query") def test_should_throw_exception(self, patch_sync_execute): @@ -1699,7 +1699,7 @@ def test_should_throw_exception(self, patch_sync_execute): self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], None, None, @@ -1717,7 +1717,7 @@ def test_to_actors_query_options(self): runner = self._create_query_runner( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], None, None, @@ -1752,7 +1752,7 @@ def test_to_actors_query_options_compare(self): runner = self._create_query_runner( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], TrendsFilter(compare=True), None, @@ -1790,7 +1790,7 @@ def test_to_actors_query_options_multiple_series(self): runner = self._create_query_runner( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview"), EventsNode(event="$pageleave")], None, None, @@ -1809,10 +1809,10 @@ def test_to_actors_query_options_breakdowns(self): runner = self._create_query_runner( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], None, - BreakdownFilter(breakdown_type=BreakdownType.event, breakdown="$browser", breakdown_limit=3), + BreakdownFilter(breakdown_type=BreakdownType.EVENT, breakdown="$browser", breakdown_limit=3), ) response = runner.to_actors_query_options() @@ -1833,10 +1833,10 @@ def test_to_actors_query_options_breakdowns_boolean(self): runner = self._create_query_runner( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], None, - BreakdownFilter(breakdown_type=BreakdownType.event, breakdown="bool_field"), + BreakdownFilter(breakdown_type=BreakdownType.EVENT, breakdown="bool_field"), ) response = runner.to_actors_query_options() @@ -1855,11 +1855,11 @@ def test_to_actors_query_options_breakdowns_histogram(self): runner = self._create_query_runner( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], None, BreakdownFilter( - breakdown_type=BreakdownType.event, + breakdown_type=BreakdownType.EVENT, breakdown="prop", breakdown_histogram_bin_count=4, ), @@ -1901,10 +1901,10 @@ def test_to_actors_query_options_breakdowns_cohort(self): runner = self._create_query_runner( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], None, - BreakdownFilter(breakdown_type=BreakdownType.cohort, breakdown=[cohort.pk]), + BreakdownFilter(breakdown_type=BreakdownType.COHORT, breakdown=[cohort.pk]), ) response = runner.to_actors_query_options() @@ -1920,10 +1920,10 @@ def test_to_actors_query_options_breakdowns_hogql(self): runner = self._create_query_runner( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], None, - BreakdownFilter(breakdown_type=BreakdownType.hogql, breakdown="properties.$browser"), + BreakdownFilter(breakdown_type=BreakdownType.HOGQL, breakdown="properties.$browser"), ) response = runner.to_actors_query_options() @@ -1944,10 +1944,10 @@ def test_to_actors_query_options_bar_value(self): runner = self._create_query_runner( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], - TrendsFilter(display=ChartDisplayType.ActionsBarValue), - BreakdownFilter(breakdown_type=BreakdownType.event, breakdown="$browser"), + TrendsFilter(display=ChartDisplayType.ACTIONS_BAR_VALUE), + BreakdownFilter(breakdown_type=BreakdownType.EVENT, breakdown="$browser"), ) response = runner.to_actors_query_options() @@ -1966,7 +1966,7 @@ def test_limit_is_context_aware(self, mock_sync_execute: MagicMock): self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], limit_context=LimitContext.QUERY_ASYNC, ) @@ -1981,7 +1981,7 @@ def test_actors_query_explicit_dates(self): runner = self._create_query_runner( "2020-01-09 12:37:42", "2020-01-20 12:37:42", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], None, None, @@ -2024,9 +2024,9 @@ def test_sampling_adjustment(self): runner = self._create_query_runner( "2020-01-01", "2020-01-31", - IntervalType.month, + IntervalType.MONTH, [EventsNode(event="$pageview")], - TrendsFilter(display=ChartDisplayType.ActionsLineGraph), + TrendsFilter(display=ChartDisplayType.ACTIONS_LINE_GRAPH), ) runner.query.samplingFactor = 0.1 response = runner.calculate() @@ -2038,9 +2038,9 @@ def test_sampling_adjustment(self): runner = self._create_query_runner( "2020-01-01", "2020-01-31", - IntervalType.month, + IntervalType.MONTH, [EventsNode(event="$pageview")], - TrendsFilter(display=ChartDisplayType.BoldNumber), + TrendsFilter(display=ChartDisplayType.BOLD_NUMBER), ) runner.query.samplingFactor = 0.1 response = runner.calculate() diff --git a/posthog/hogql_queries/insights/trends/test/test_utils.py b/posthog/hogql_queries/insights/trends/test/test_utils.py index 5fecab14914b7..e4527fae76e31 100644 --- a/posthog/hogql_queries/insights/trends/test/test_utils.py +++ b/posthog/hogql_queries/insights/trends/test/test_utils.py @@ -4,58 +4,60 @@ def test_properties_chain_person(): - p1 = get_properties_chain(breakdown_type=BreakdownType.person, breakdown_field="field", group_type_index=None) + p1 = get_properties_chain(breakdown_type=BreakdownType.PERSON, breakdown_field="field", group_type_index=None) assert p1 == ["person", "properties", "field"] - p2 = get_properties_chain(breakdown_type=BreakdownType.person, breakdown_field="field", group_type_index=1) + p2 = get_properties_chain(breakdown_type=BreakdownType.PERSON, breakdown_field="field", group_type_index=1) assert p2 == ["person", "properties", "field"] def test_properties_chain_session(): - p1 = get_properties_chain(breakdown_type=BreakdownType.session, breakdown_field="anything", group_type_index=None) + p1 = get_properties_chain(breakdown_type=BreakdownType.SESSION, breakdown_field="anything", group_type_index=None) assert p1 == ["session", "anything"] - p2 = get_properties_chain(breakdown_type=BreakdownType.session, breakdown_field="anything", group_type_index=1) + p2 = get_properties_chain(breakdown_type=BreakdownType.SESSION, breakdown_field="anything", group_type_index=1) assert p2 == ["session", "anything"] p3 = get_properties_chain( - breakdown_type=BreakdownType.session, breakdown_field="$session_duration", group_type_index=None + breakdown_type=BreakdownType.SESSION, breakdown_field="$session_duration", group_type_index=None ) assert p3 == ["session", "$session_duration"] def test_properties_chain_groups(): - p1 = get_properties_chain(breakdown_type=BreakdownType.group, breakdown_field="anything", group_type_index=1) + p1 = get_properties_chain(breakdown_type=BreakdownType.GROUP, breakdown_field="anything", group_type_index=1) assert p1 == ["group_1", "properties", "anything"] with pytest.raises(Exception) as e: - get_properties_chain(breakdown_type=BreakdownType.group, breakdown_field="anything", group_type_index=None) + get_properties_chain(breakdown_type=BreakdownType.GROUP, breakdown_field="anything", group_type_index=None) assert "group_type_index missing from params" in str(e.value) def test_properties_chain_events(): - p1 = get_properties_chain(breakdown_type=BreakdownType.event, breakdown_field="anything", group_type_index=None) + p1 = get_properties_chain(breakdown_type=BreakdownType.EVENT, breakdown_field="anything", group_type_index=None) assert p1 == ["properties", "anything"] - p2 = get_properties_chain(breakdown_type=BreakdownType.event, breakdown_field="anything_else", group_type_index=1) + p2 = get_properties_chain(breakdown_type=BreakdownType.EVENT, breakdown_field="anything_else", group_type_index=1) assert p2 == ["properties", "anything_else"] def test_properties_chain_warehouse_props(): p1 = get_properties_chain( - breakdown_type=BreakdownType.data_warehouse_person_property, + breakdown_type=BreakdownType.DATA_WAREHOUSE_PERSON_PROPERTY, breakdown_field="some_table.field", group_type_index=None, ) assert p1 == ["person", "some_table", "field"] p2 = get_properties_chain( - breakdown_type=BreakdownType.data_warehouse_person_property, breakdown_field="some_table", group_type_index=None + breakdown_type=BreakdownType.DATA_WAREHOUSE_PERSON_PROPERTY, + breakdown_field="some_table", + group_type_index=None, ) assert p2 == ["person", "some_table"] p3 = get_properties_chain( - breakdown_type=BreakdownType.data_warehouse_person_property, + breakdown_type=BreakdownType.DATA_WAREHOUSE_PERSON_PROPERTY, breakdown_field="some_table.props.obj.blah", group_type_index=None, ) diff --git a/posthog/hogql_queries/insights/trends/trends_actors_query_builder.py b/posthog/hogql_queries/insights/trends/trends_actors_query_builder.py index 1eff2f0aae9cc..c29be46cc791c 100644 --- a/posthog/hogql_queries/insights/trends/trends_actors_query_builder.py +++ b/posthog/hogql_queries/insights/trends/trends_actors_query_builder.py @@ -119,7 +119,7 @@ def trends_aggregation_operations(self) -> AggregationOperations: def is_compare_previous(self) -> bool: return ( bool(self.trends_query.trendsFilter and self.trends_query.trendsFilter.compare) - and self.compare_value == Compare.previous + and self.compare_value == Compare.PREVIOUS ) @cached_property @@ -128,11 +128,11 @@ def is_active_users_math(self) -> bool: @cached_property def is_weekly_active_math(self) -> bool: - return self.entity.math == BaseMathType.weekly_active + return self.entity.math == BaseMathType.WEEKLY_ACTIVE @cached_property def is_monthly_active_math(self) -> bool: - return self.entity.math == BaseMathType.monthly_active + return self.entity.math == BaseMathType.MONTHLY_ACTIVE @cached_property def is_hourly(self) -> bool: diff --git a/posthog/hogql_queries/insights/trends/trends_query_builder.py b/posthog/hogql_queries/insights/trends/trends_query_builder.py index 00b7fad057384..015e269e5628e 100644 --- a/posthog/hogql_queries/insights/trends/trends_query_builder.py +++ b/posthog/hogql_queries/insights/trends/trends_query_builder.py @@ -136,7 +136,7 @@ def _get_events_subquery( # For cumulative unique users or groups, we want to count each user or group once per query, not per day if ( self.query.trendsFilter - and self.query.trendsFilter.display == ChartDisplayType.ActionsLineGraphCumulative + and self.query.trendsFilter.display == ChartDisplayType.ACTIONS_LINE_GRAPH_CUMULATIVE and (self.series.math == "unique_group" or self.series.math == "dau") ): day_start.expr = ast.Call(name="min", args=[day_start.expr]) @@ -237,7 +237,8 @@ def _get_events_subquery( return default_query def _outer_select_query(self, breakdown: Breakdown, inner_query: ast.SelectQuery) -> ast.SelectQuery: - total_array = parse_expr(""" + total_array = parse_expr( + """ arrayMap( _match_date -> arraySum( @@ -249,9 +250,10 @@ def _outer_select_query(self, breakdown: Breakdown, inner_query: ast.SelectQuery ), date ) - """) + """ + ) - if self._trends_display.display_type == ChartDisplayType.ActionsLineGraphCumulative: + if self._trends_display.display_type == ChartDisplayType.ACTIONS_LINE_GRAPH_CUMULATIVE: # fill zeros in with the previous value total_array = parse_expr( """ diff --git a/posthog/hogql_queries/insights/trends/trends_query_runner.py b/posthog/hogql_queries/insights/trends/trends_query_runner.py index cbd5f87e1c4c5..d64e80a38330a 100644 --- a/posthog/hogql_queries/insights/trends/trends_query_runner.py +++ b/posthog/hogql_queries/insights/trends/trends_query_runner.py @@ -157,7 +157,7 @@ def to_actors_query( include_recordings: Optional[bool] = None, ) -> ast.SelectQuery | ast.SelectUnionQuery: with self.timings.measure("trends_to_actors_query"): - if self.query.breakdownFilter and self.query.breakdownFilter.breakdown_type == BreakdownType.cohort: + if self.query.breakdownFilter and self.query.breakdownFilter.breakdown_type == BreakdownType.COHORT: if self.query.breakdownFilter.breakdown in ("all", ["all"]) or breakdown_value == "all": self.query.breakdownFilter = None elif isinstance(self.query.breakdownFilter.breakdown, list): @@ -443,7 +443,7 @@ def get_value(name: str, val: Any): }, } else: - if self._trends_display.display_type == ChartDisplayType.ActionsLineGraphCumulative: + if self._trends_display.display_type == ChartDisplayType.ACTIONS_LINE_GRAPH_CUMULATIVE: count = get_value("total", val)[-1] else: count = float(sum(get_value("total", val))) @@ -587,14 +587,14 @@ def series_event(self, series: Union[EventsNode, ActionsNode, DataWarehouseNode] def update_hogql_modifiers(self) -> None: if ( - self.modifiers.inCohortVia == InCohortVia.auto + self.modifiers.inCohortVia == InCohortVia.AUTO and self.query.breakdownFilter is not None and self.query.breakdownFilter.breakdown_type == "cohort" and isinstance(self.query.breakdownFilter.breakdown, list) and len(self.query.breakdownFilter.breakdown) > 1 and not any(value == "all" for value in self.query.breakdownFilter.breakdown) ): - self.modifiers.inCohortVia = InCohortVia.leftjoin_conjoined + self.modifiers.inCohortVia = InCohortVia.LEFTJOIN_CONJOINED datawarehouse_modifiers = [] for series in self.query.series: @@ -623,7 +623,7 @@ def setup_series(self) -> list[SeriesWithExtras]: ] if ( - self.modifiers.inCohortVia != InCohortVia.leftjoin_conjoined + self.modifiers.inCohortVia != InCohortVia.LEFTJOIN_CONJOINED and self.query.breakdownFilter is not None and self.query.breakdownFilter.breakdown_type == "cohort" ): @@ -696,7 +696,7 @@ def apply_formula( and self.query.breakdownFilter.breakdown_type == "cohort" and isinstance(self.query.breakdownFilter.breakdown, list) and "all" in self.query.breakdownFilter.breakdown - and self.modifiers.inCohortVia != InCohortVia.leftjoin_conjoined + and self.modifiers.inCohortVia != InCohortVia.LEFTJOIN_CONJOINED and not in_breakdown_clause and self.query.trendsFilter and self.query.trendsFilter.formula @@ -888,7 +888,7 @@ def _query_to_filter(self) -> dict[str, Any]: @cached_property def _trends_display(self) -> TrendsDisplay: if self.query.trendsFilter is None or self.query.trendsFilter.display is None: - display = ChartDisplayType.ActionsLineGraph + display = ChartDisplayType.ACTIONS_LINE_GRAPH else: display = self.query.trendsFilter.display diff --git a/posthog/hogql_queries/insights/utils/test/test_entities.py b/posthog/hogql_queries/insights/utils/test/test_entities.py index df89b129031b4..587f9e8c9cc3f 100644 --- a/posthog/hogql_queries/insights/utils/test/test_entities.py +++ b/posthog/hogql_queries/insights/utils/test/test_entities.py @@ -22,10 +22,10 @@ (ActionsNode(id=1), ActionsNode(id=2), False), ( EventsNode( - properties=[EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.exact)] + properties=[EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.EXACT)] ), EventsNode( - properties=[EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.exact)] + properties=[EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.EXACT)] ), True, ), @@ -44,50 +44,50 @@ # different type ( EventsNode( - properties=[PersonPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.exact)] + properties=[PersonPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.EXACT)] ), EventsNode( - properties=[EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.exact)] + properties=[EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.EXACT)] ), False, ), # different key ( EventsNode( - properties=[EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.exact)] + properties=[EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.EXACT)] ), EventsNode( - properties=[EventPropertyFilter(key="other_key", value="some_value", operator=PropertyOperator.exact)] + properties=[EventPropertyFilter(key="other_key", value="some_value", operator=PropertyOperator.EXACT)] ), False, ), # different value ( EventsNode( - properties=[EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.exact)] + properties=[EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.EXACT)] ), EventsNode( - properties=[EventPropertyFilter(key="some_key", value="other_value", operator=PropertyOperator.exact)] + properties=[EventPropertyFilter(key="some_key", value="other_value", operator=PropertyOperator.EXACT)] ), False, ), # different operator ( EventsNode( - properties=[EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.exact)] + properties=[EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.EXACT)] ), EventsNode( - properties=[EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.is_not)] + properties=[EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.IS_NOT)] ), False, ), # different fixed properties ( EventsNode( - fixedProperties=[EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.exact)] + fixedProperties=[EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.EXACT)] ), EventsNode( - fixedProperties=[EventPropertyFilter(key="other_key", value="some_value", operator=PropertyOperator.exact)] + fixedProperties=[EventPropertyFilter(key="other_key", value="some_value", operator=PropertyOperator.EXACT)] ), False, ), @@ -103,10 +103,10 @@ def test_is_equal(a, b, expected): # everything equal ( EventsNode( - properties=[EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.exact)] + properties=[EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.EXACT)] ), EventsNode( - properties=[EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.exact)] + properties=[EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.EXACT)] ), True, ), @@ -114,24 +114,24 @@ def test_is_equal(a, b, expected): ( EventsNode( properties=[ - EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.exact), - PersonPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.exact), + EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.EXACT), + PersonPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.EXACT), ] ), EventsNode( - properties=[EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.exact)] + properties=[EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.EXACT)] ), True, ), # subset ( EventsNode( - properties=[EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.exact)] + properties=[EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.EXACT)] ), EventsNode( properties=[ - EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.exact), - PersonPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.exact), + EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.EXACT), + PersonPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.EXACT), ] ), False, @@ -154,10 +154,10 @@ def test_is_equal(a, b, expected): # different type ( EventsNode( - properties=[PersonPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.exact)] + properties=[PersonPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.EXACT)] ), EventsNode( - properties=[EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.exact)] + properties=[EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.EXACT)] ), False, ), diff --git a/posthog/hogql_queries/legacy_compatibility/filter_to_query.py b/posthog/hogql_queries/legacy_compatibility/filter_to_query.py index 5510b198257df..cad685ec9675e 100644 --- a/posthog/hogql_queries/legacy_compatibility/filter_to_query.py +++ b/posthog/hogql_queries/legacy_compatibility/filter_to_query.py @@ -40,16 +40,16 @@ class MathAvailability(str, Enum): actors_only_math_types = [ - BaseMathType.dau, - BaseMathType.weekly_active, - BaseMathType.monthly_active, + BaseMathType.DAU, + BaseMathType.WEEKLY_ACTIVE, + BaseMathType.MONTHLY_ACTIVE, "unique_group", "hogql", ] def clean_display(display: str): - if display not in ChartDisplayType.__members__: + if display not in [c.value for c in ChartDisplayType]: return None else: return display @@ -81,7 +81,7 @@ def legacy_entity_to_node( and math_availability == MathAvailability.ActorsOnly and entity.math not in actors_only_math_types ): - shared = {**shared, "math": BaseMathType.dau} + shared = {**shared, "math": BaseMathType.DAU} else: shared = { **shared, @@ -321,7 +321,7 @@ def _insight_filter(filter: dict): # Backwards compatibility # Before Filter.funnel_viz_type funnel trends were indicated by Filter.display being TRENDS_LINEAR if funnel_viz_type is None and filter.get("display") == "ActionsLineGraph": - funnel_viz_type = FunnelVizType.trends + funnel_viz_type = FunnelVizType.TRENDS insight_filter = { "funnelsFilter": FunnelsFilter( diff --git a/posthog/hogql_queries/legacy_compatibility/test/test_filter_to_query.py b/posthog/hogql_queries/legacy_compatibility/test/test_filter_to_query.py index 13f5ea9f4d7f6..87c007c110833 100644 --- a/posthog/hogql_queries/legacy_compatibility/test/test_filter_to_query.py +++ b/posthog/hogql_queries/legacy_compatibility/test/test_filter_to_query.py @@ -1007,9 +1007,9 @@ def test_series_custom(self): query.series, [ ActionsNode(id=1), - ActionsNode(id=1, math=BaseMathType.dau), + ActionsNode(id=1, math=BaseMathType.DAU), EventsNode(event="$pageview", name="$pageview"), - EventsNode(event="$pageview", name="$pageview", math=BaseMathType.dau), + EventsNode(event="$pageview", name="$pageview", math=BaseMathType.DAU), ], ) @@ -1037,7 +1037,7 @@ def test_series_data_warehouse(self): DataWarehouseNode( id="some_table", name="some_table", - math=BaseMathType.total, + math=BaseMathType.TOTAL, table_name="some_table", id_field="id", timestamp_field="created_at", @@ -1061,9 +1061,9 @@ def test_series_order(self): self.assertEqual( query.series, [ - ActionsNode(id=1, math=BaseMathType.dau), + ActionsNode(id=1, math=BaseMathType.DAU), EventsNode(event="$pageview", name="$pageview"), - EventsNode(event="$pageview", name="$pageview", math=BaseMathType.dau), + EventsNode(event="$pageview", name="$pageview", math=BaseMathType.DAU), ActionsNode(id=1), ], ) @@ -1100,23 +1100,23 @@ def test_series_math(self): self.assertEqual( query.series, [ - EventsNode(event="$pageview", name="$pageview", math=BaseMathType.dau), + EventsNode(event="$pageview", name="$pageview", math=BaseMathType.DAU), EventsNode( event="$pageview", name="$pageview", - math=PropertyMathType.median, + math=PropertyMathType.MEDIAN, math_property="$math_prop", ), EventsNode( event="$pageview", name="$pageview", - math=CountPerActorMathType.avg_count_per_actor, + math=CountPerActorMathType.AVG_COUNT_PER_ACTOR, ), EventsNode( event="$pageview", name="$pageview", math="unique_group", - math_group_type_index=MathGroupTypeIndex.number_0, + math_group_type_index=MathGroupTypeIndex.NUMBER_0, ), EventsNode( event="$pageview", @@ -1235,7 +1235,7 @@ def test_series_properties(self): EventPropertyFilter( key="success", value=["true"], - operator=PropertyOperator.exact, + operator=PropertyOperator.EXACT, ) ], ), @@ -1246,7 +1246,7 @@ def test_series_properties(self): PersonPropertyFilter( key="email", value="is_set", - operator=PropertyOperator.is_set, + operator=PropertyOperator.IS_SET, ) ], ), @@ -1255,16 +1255,16 @@ def test_series_properties(self): name="$pageview", properties=[ ElementPropertyFilter( - key=Key.text, + key=Key.TEXT, value=["some text"], - operator=PropertyOperator.exact, + operator=PropertyOperator.EXACT, ) ], ), EventsNode( event="$pageview", name="$pageview", - properties=[SessionPropertyFilter(key="$session_duration", value=1, operator=PropertyOperator.gt)], + properties=[SessionPropertyFilter(key="$session_duration", value=1, operator=PropertyOperator.GT)], ), EventsNode( event="$pageview", @@ -1278,7 +1278,7 @@ def test_series_properties(self): GroupPropertyFilter( key="name", value=["Hedgebox Inc."], - operator=PropertyOperator.exact, + operator=PropertyOperator.EXACT, group_type_index=2, ) ], @@ -1295,12 +1295,12 @@ def test_series_properties(self): EventPropertyFilter( key="$referring_domain", value="google", - operator=PropertyOperator.icontains, + operator=PropertyOperator.ICONTAINS, ), EventPropertyFilter( key="utm_source", value="is_not_set", - operator=PropertyOperator.is_not_set, + operator=PropertyOperator.IS_NOT_SET, ), ], ), @@ -1315,7 +1315,7 @@ def test_breakdown(self): assert isinstance(query, TrendsQuery) self.assertEqual( query.breakdownFilter, - BreakdownFilter(breakdown_type=BreakdownType.event, breakdown="$browser"), + BreakdownFilter(breakdown_type=BreakdownType.EVENT, breakdown="$browser"), ) def test_breakdown_converts_multi(self): @@ -1326,7 +1326,7 @@ def test_breakdown_converts_multi(self): assert isinstance(query, TrendsQuery) self.assertEqual( query.breakdownFilter, - BreakdownFilter(breakdown_type=BreakdownType.event, breakdown="$browser"), + BreakdownFilter(breakdown_type=BreakdownType.EVENT, breakdown="$browser"), ) def test_breakdown_type_default(self): @@ -1337,7 +1337,7 @@ def test_breakdown_type_default(self): assert isinstance(query, TrendsQuery) self.assertEqual( query.breakdownFilter, - BreakdownFilter(breakdown_type=BreakdownType.event, breakdown="some_prop"), + BreakdownFilter(breakdown_type=BreakdownType.EVENT, breakdown="some_prop"), ) def test_trends_filter(self): @@ -1363,11 +1363,11 @@ def test_trends_filter(self): TrendsFilter( smoothingIntervals=2, compare=True, - aggregationAxisFormat=AggregationAxisFormat.duration_ms, + aggregationAxisFormat=AggregationAxisFormat.DURATION_MS, aggregationAxisPrefix="pre", aggregationAxisPostfix="post", formula="A + B", - display=ChartDisplayType.ActionsAreaGraph, + display=ChartDisplayType.ACTIONS_AREA_GRAPH, decimalPlaces=5, showLegend=True, showPercentStackView=True, @@ -1427,14 +1427,14 @@ def test_funnels_filter(self): self.assertEqual( query.funnelsFilter, FunnelsFilter( - funnelVizType=FunnelVizType.steps, + funnelVizType=FunnelVizType.STEPS, funnelFromStep=1, funnelToStep=2, - funnelWindowIntervalUnit=FunnelConversionWindowTimeUnit.hour, + funnelWindowIntervalUnit=FunnelConversionWindowTimeUnit.HOUR, funnelWindowInterval=13, - breakdownAttributionType=BreakdownAttributionType.step, + breakdownAttributionType=BreakdownAttributionType.STEP, breakdownAttributionValue=2, - funnelOrderType=StepOrderValue.strict, + funnelOrderType=StepOrderValue.STRICT, exclusions=[ FunnelExclusionEventsNode( event="$pageview", @@ -1477,9 +1477,9 @@ def test_retention_filter(self): self.assertEqual( query.retentionFilter, RetentionFilter( - retentionType=RetentionType.retention_first_time, + retentionType=RetentionType.RETENTION_FIRST_TIME, totalIntervals=12, - period=RetentionPeriod.Week, + period=RetentionPeriod.WEEK, returningEntity={ "id": "$pageview", "name": "$pageview", @@ -1539,7 +1539,7 @@ def test_paths_filter(self): self.assertEqual( query.pathsFilter, PathsFilter( - includeEventTypes=[PathType.field_pageview, PathType.hogql], + includeEventTypes=[PathType.FIELD_PAGEVIEW, PathType.HOGQL], pathsHogQLExpression="event", startPoint="http://localhost:8000/events", endPoint="http://localhost:8000/home", @@ -1558,14 +1558,14 @@ def test_paths_filter(self): self.assertEqual( query.funnelPathsFilter, FunnelPathsFilter( - funnelPathType=FunnelPathType.funnel_path_between_steps, + funnelPathType=FunnelPathType.FUNNEL_PATH_BETWEEN_STEPS, funnelSource=FunnelsQuery( series=[ EventsNode(event="$pageview", name="$pageview"), EventsNode(event=None, name="All events"), ], filterTestAccounts=True, - funnelsFilter=FunnelsFilter(funnelVizType=FunnelVizType.steps, exclusions=[]), + funnelsFilter=FunnelsFilter(funnelVizType=FunnelVizType.STEPS, exclusions=[]), breakdownFilter=BreakdownFilter(), dateRange=InsightDateRange(), ), @@ -1605,6 +1605,6 @@ def test_lifecycle_filter(self): query.lifecycleFilter, LifecycleFilter( showValuesOnSeries=True, - toggledLifecycles=[LifecycleToggle.new, LifecycleToggle.dormant], + toggledLifecycles=[LifecycleToggle.NEW, LifecycleToggle.DORMANT], ), ) diff --git a/posthog/hogql_queries/query_runner.py b/posthog/hogql_queries/query_runner.py index 850ca5b663e3d..8ea3e2e806e79 100644 --- a/posthog/hogql_queries/query_runner.py +++ b/posthog/hogql_queries/query_runner.py @@ -264,31 +264,62 @@ def get_query_runner( team=team, timings=timings, modifiers=modifiers, + limit_context=limit_context, ) if kind == "WebOverviewQuery": use_session_table = get_from_dict_or_attr(query, "useSessionsTable") if use_session_table: from .web_analytics.web_overview import WebOverviewQueryRunner - return WebOverviewQueryRunner(query=query, team=team, timings=timings, modifiers=modifiers) + return WebOverviewQueryRunner( + query=query, + team=team, + timings=timings, + modifiers=modifiers, + limit_context=limit_context, + ) else: from .web_analytics.web_overview_legacy import LegacyWebOverviewQueryRunner - return LegacyWebOverviewQueryRunner(query=query, team=team, timings=timings, modifiers=modifiers) + return LegacyWebOverviewQueryRunner( + query=query, + team=team, + timings=timings, + modifiers=modifiers, + limit_context=limit_context, + ) if kind == "WebTopClicksQuery": from .web_analytics.top_clicks import WebTopClicksQueryRunner - return WebTopClicksQueryRunner(query=query, team=team, timings=timings, modifiers=modifiers) + return WebTopClicksQueryRunner( + query=query, + team=team, + timings=timings, + modifiers=modifiers, + limit_context=limit_context, + ) if kind == "WebStatsTableQuery": use_session_table = get_from_dict_or_attr(query, "useSessionsTable") if use_session_table: from .web_analytics.stats_table import WebStatsTableQueryRunner - return WebStatsTableQueryRunner(query=query, team=team, timings=timings, modifiers=modifiers) + return WebStatsTableQueryRunner( + query=query, + team=team, + timings=timings, + modifiers=modifiers, + limit_context=limit_context, + ) else: from .web_analytics.stats_table_legacy import LegacyWebStatsTableQueryRunner - return LegacyWebStatsTableQueryRunner(query=query, team=team, timings=timings, modifiers=modifiers) + return LegacyWebStatsTableQueryRunner( + query=query, + team=team, + timings=timings, + modifiers=modifiers, + limit_context=limit_context, + ) raise ValueError(f"Can't get a runner for an unknown query kind: {kind}") @@ -543,13 +574,15 @@ def apply_dashboard_filters(self, dashboard_filter: DashboardFilter): if self.query.properties: try: self.query.properties = PropertyGroupFilter( - type=FilterLogicalOperator.AND, + type=FilterLogicalOperator.AND_, values=[ - PropertyGroupFilterValue(type=FilterLogicalOperator.AND, values=self.query.properties) - if isinstance(self.query.properties, list) - else PropertyGroupFilterValue(**self.query.properties.model_dump()), + ( + PropertyGroupFilterValue(type=FilterLogicalOperator.AND_, values=self.query.properties) + if isinstance(self.query.properties, list) + else PropertyGroupFilterValue(**self.query.properties.model_dump()) + ), PropertyGroupFilterValue( - type=FilterLogicalOperator.AND, values=dashboard_filter.properties + type=FilterLogicalOperator.AND_, values=dashboard_filter.properties ), ], ) diff --git a/posthog/hogql_queries/test/test_actors_query_runner.py b/posthog/hogql_queries/test/test_actors_query_runner.py index d6bed9fec969b..904c1adad8d9f 100644 --- a/posthog/hogql_queries/test/test_actors_query_runner.py +++ b/posthog/hogql_queries/test/test_actors_query_runner.py @@ -94,7 +94,7 @@ def test_persons_query_properties(self): PersonPropertyFilter( key="random_uuid", value=self.random_uuid, - operator=PropertyOperator.exact, + operator=PropertyOperator.EXACT, ), HogQLPropertyFilter(key="toInt(properties.index) > 5"), ] @@ -110,7 +110,7 @@ def test_persons_query_fixed_properties(self): PersonPropertyFilter( key="random_uuid", value=self.random_uuid, - operator=PropertyOperator.exact, + operator=PropertyOperator.EXACT, ), HogQLPropertyFilter(key="toInt(properties.index) < 2"), ] @@ -221,10 +221,10 @@ def test_source_lifecycle_query(self): PersonPropertyFilter( key="random_uuid", value=self.random_uuid, - operator=PropertyOperator.exact, + operator=PropertyOperator.EXACT, ) ], - interval=IntervalType.day, + interval=IntervalType.DAY, dateRange=InsightDateRange(date_from="-7d"), ) query = ActorsQuery( diff --git a/posthog/hogql_queries/test/test_events_query_runner.py b/posthog/hogql_queries/test/test_events_query_runner.py index 345df985bf6fa..f42fe3dc65755 100644 --- a/posthog/hogql_queries/test/test_events_query_runner.py +++ b/posthog/hogql_queries/test/test_events_query_runner.py @@ -97,8 +97,8 @@ def test_is_not_set_boolean(self): EventPropertyFilter( type="event", key="boolean_field", - operator=PropertyOperator.is_not_set, - value=PropertyOperator.is_not_set, + operator=PropertyOperator.IS_NOT_SET, + value=PropertyOperator.IS_NOT_SET, ) ) @@ -111,8 +111,8 @@ def test_is_set_boolean(self): EventPropertyFilter( type="event", key="boolean_field", - operator=PropertyOperator.is_set, - value=PropertyOperator.is_set, + operator=PropertyOperator.IS_SET, + value=PropertyOperator.IS_SET, ) ) diff --git a/posthog/hogql_queries/test/test_query_runner.py b/posthog/hogql_queries/test/test_query_runner.py index 6b43b0c76e2da..96003badffd03 100644 --- a/posthog/hogql_queries/test/test_query_runner.py +++ b/posthog/hogql_queries/test/test_query_runner.py @@ -200,7 +200,7 @@ def test_modifier_passthrough(self): runner = HogQLQueryRunner( query=HogQLQuery(query="select properties.$browser from events"), team=self.team, - modifiers=HogQLQueryModifiers(materializationMode=MaterializationMode.legacy_null_as_string), + modifiers=HogQLQueryModifiers(materializationMode=MaterializationMode.LEGACY_NULL_AS_STRING), ) response = runner.calculate() assert response.clickhouse is not None @@ -209,7 +209,7 @@ def test_modifier_passthrough(self): runner = HogQLQueryRunner( query=HogQLQuery(query="select properties.$browser from events"), team=self.team, - modifiers=HogQLQueryModifiers(materializationMode=MaterializationMode.disabled), + modifiers=HogQLQueryModifiers(materializationMode=MaterializationMode.DISABLED), ) response = runner.calculate() assert response.clickhouse is not None diff --git a/posthog/hogql_queries/utils/query_date_range.py b/posthog/hogql_queries/utils/query_date_range.py index ecdf9411b0054..1f5d5bf7996a1 100644 --- a/posthog/hogql_queries/utils/query_date_range.py +++ b/posthog/hogql_queries/utils/query_date_range.py @@ -38,10 +38,10 @@ def __init__( ) -> None: self._team = team self._date_range = date_range - self._interval = interval or IntervalType.day + self._interval = interval or IntervalType.DAY self._now_without_timezone = now - if not isinstance(self._interval, IntervalType) or re.match(r"[^a-z]", self._interval.name): + if not isinstance(self._interval, IntervalType) or re.match(r"[^a-z]", "DAY", re.IGNORECASE): raise ValueError(f"Invalid interval: {interval}") def date_to(self) -> datetime: @@ -114,18 +114,18 @@ def previous_period_date_from_str(self) -> str: @cached_property def interval_type(self) -> IntervalType: - return self._interval or IntervalType.day + return self._interval or IntervalType.DAY @cached_property def interval_name(self) -> IntervalLiteral: - return cast(IntervalLiteral, self.interval_type.name) + return cast(IntervalLiteral, self.interval_type.name.lower()) @cached_property def is_hourly(self) -> bool: if self._interval is None: return False - return self._interval == IntervalType.hour + return self._interval == IntervalType.HOUR @cached_property def explicit(self) -> bool: @@ -229,9 +229,9 @@ def use_start_of_interval(self): is_delta_hours = delta_mapping.get("hours", None) is not None - if interval in (IntervalType.hour, IntervalType.minute): + if interval in (IntervalType.HOUR, IntervalType.MINUTE): return False - elif interval == IntervalType.day: + elif interval == IntervalType.DAY: if is_delta_hours: return False return True @@ -310,15 +310,15 @@ def determine_time_delta(total_intervals: int, period: str) -> timedelta: def date_from(self) -> datetime: delta = self.determine_time_delta(self.total_intervals, self._interval.name) - if self._interval in (IntervalType.hour, IntervalType.minute): + if self._interval in (IntervalType.HOUR, IntervalType.MINUTE): return self.date_to() - delta - elif self._interval == IntervalType.week: + elif self._interval == IntervalType.WEEK: date_from = self.date_to() - delta week_start_alignment_days = date_from.isoweekday() % 7 if self._team.week_start_day == WeekStartDay.MONDAY: week_start_alignment_days = date_from.weekday() return date_from - timedelta(days=week_start_alignment_days) - elif self._interval == IntervalType.month: + elif self._interval == IntervalType.MONTH: return self.date_to().replace(day=1, hour=0, minute=0, second=0, microsecond=0) - delta else: date_to = self.date_to().replace(hour=0, minute=0, second=0, microsecond=0) diff --git a/posthog/hogql_queries/utils/test/test_query_date_range.py b/posthog/hogql_queries/utils/test/test_query_date_range.py index c645df7744802..c87c29ebcbd72 100644 --- a/posthog/hogql_queries/utils/test/test_query_date_range.py +++ b/posthog/hogql_queries/utils/test/test_query_date_range.py @@ -13,14 +13,14 @@ class TestQueryDateRange(APIBaseTest): def test_parsed_date(self): now = parser.isoparse("2021-08-25T00:00:00.000Z") date_range = InsightDateRange(date_from="-48h") - query_date_range = QueryDateRange(team=self.team, date_range=date_range, interval=IntervalType.day, now=now) + query_date_range = QueryDateRange(team=self.team, date_range=date_range, interval=IntervalType.DAY, now=now) self.assertEqual(query_date_range.date_from(), parser.isoparse("2021-08-23T00:00:00Z")) self.assertEqual(query_date_range.date_to(), parser.isoparse("2021-08-25T23:59:59.999999Z")) def test_parsed_date_hour(self): now = parser.isoparse("2021-08-25T00:00:00.000Z") date_range = InsightDateRange(date_from="-48h") - query_date_range = QueryDateRange(team=self.team, date_range=date_range, interval=IntervalType.hour, now=now) + query_date_range = QueryDateRange(team=self.team, date_range=date_range, interval=IntervalType.HOUR, now=now) self.assertEqual(query_date_range.date_from(), parser.isoparse("2021-08-23T00:00:00Z")) self.assertEqual( @@ -30,7 +30,7 @@ def test_parsed_date_hour(self): def test_parsed_date_middle_of_hour(self): now = parser.isoparse("2021-08-25T00:00:00.000Z") date_range = InsightDateRange(date_from="2021-08-23 05:00:00", date_to="2021-08-26 07:00:00") - query_date_range = QueryDateRange(team=self.team, date_range=date_range, interval=IntervalType.hour, now=now) + query_date_range = QueryDateRange(team=self.team, date_range=date_range, interval=IntervalType.HOUR, now=now) self.assertEqual(query_date_range.date_from(), parser.isoparse("2021-08-23 05:00:00Z")) self.assertEqual( @@ -40,7 +40,7 @@ def test_parsed_date_middle_of_hour(self): def test_parsed_date_week(self): now = parser.isoparse("2021-08-25T00:00:00.000Z") date_range = InsightDateRange(date_from="-7d") - query_date_range = QueryDateRange(team=self.team, date_range=date_range, interval=IntervalType.week, now=now) + query_date_range = QueryDateRange(team=self.team, date_range=date_range, interval=IntervalType.WEEK, now=now) self.assertEqual(query_date_range.date_from(), parser.isoparse("2021-08-18 00:00:00Z")) self.assertEqual(query_date_range.date_to(), parser.isoparse("2021-08-25 23:59:59.999999Z")) @@ -49,13 +49,13 @@ def test_all_values(self): now = parser.isoparse("2021-08-25T00:00:00.000Z") self.assertEqual( QueryDateRange( - team=self.team, date_range=InsightDateRange(date_from="-20h"), interval=IntervalType.day, now=now + team=self.team, date_range=InsightDateRange(date_from="-20h"), interval=IntervalType.DAY, now=now ).all_values(), [parser.isoparse("2021-08-24T00:00:00Z"), parser.isoparse("2021-08-25T00:00:00Z")], ) self.assertEqual( QueryDateRange( - team=self.team, date_range=InsightDateRange(date_from="-20d"), interval=IntervalType.week, now=now + team=self.team, date_range=InsightDateRange(date_from="-20d"), interval=IntervalType.WEEK, now=now ).all_values(), [ parser.isoparse("2021-08-01T00:00:00Z"), @@ -67,7 +67,7 @@ def test_all_values(self): self.team.week_start_day = WeekStartDay.MONDAY self.assertEqual( QueryDateRange( - team=self.team, date_range=InsightDateRange(date_from="-20d"), interval=IntervalType.week, now=now + team=self.team, date_range=InsightDateRange(date_from="-20d"), interval=IntervalType.WEEK, now=now ).all_values(), [ parser.isoparse("2021-08-02T00:00:00Z"), @@ -78,13 +78,13 @@ def test_all_values(self): ) self.assertEqual( QueryDateRange( - team=self.team, date_range=InsightDateRange(date_from="-50d"), interval=IntervalType.month, now=now + team=self.team, date_range=InsightDateRange(date_from="-50d"), interval=IntervalType.MONTH, now=now ).all_values(), [parser.isoparse("2021-07-01T00:00:00Z"), parser.isoparse("2021-08-01T00:00:00Z")], ) self.assertEqual( QueryDateRange( - team=self.team, date_range=InsightDateRange(date_from="-3h"), interval=IntervalType.hour, now=now + team=self.team, date_range=InsightDateRange(date_from="-3h"), interval=IntervalType.HOUR, now=now ).all_values(), [ parser.isoparse("2021-08-24T21:00:00Z"), @@ -99,7 +99,7 @@ def test_date_to_explicit(self): date_range = InsightDateRange( date_from="2021-02-25T12:25:23.000Z", date_to="2021-04-25T10:59:23.000Z", explicitDate=True ) - query_date_range = QueryDateRange(team=self.team, date_range=date_range, interval=IntervalType.day, now=now) + query_date_range = QueryDateRange(team=self.team, date_range=date_range, interval=IntervalType.DAY, now=now) self.assertEqual(query_date_range.date_from(), parser.isoparse("2021-02-25T12:25:23.000Z")) self.assertEqual(query_date_range.date_to(), parser.isoparse("2021-04-25T10:59:23.000Z")) @@ -108,12 +108,12 @@ def test_yesterday(self): now = parser.isoparse("2021-08-25T00:00:00.000Z") date_range = InsightDateRange(date_from="-1dStart", date_to="-1dEnd", explicitDate=False) - query_date_range = QueryDateRange(team=self.team, date_range=date_range, interval=IntervalType.hour, now=now) + query_date_range = QueryDateRange(team=self.team, date_range=date_range, interval=IntervalType.HOUR, now=now) self.assertEqual(query_date_range.date_from(), parser.isoparse("2021-08-24T00:00:00.000000Z")) self.assertEqual(query_date_range.date_to(), parser.isoparse("2021-08-24T23:59:59.999999Z")) - query_date_range = QueryDateRange(team=self.team, date_range=date_range, interval=IntervalType.day, now=now) + query_date_range = QueryDateRange(team=self.team, date_range=date_range, interval=IntervalType.DAY, now=now) self.assertEqual(query_date_range.date_from(), parser.isoparse("2021-08-24T00:00:00.000000Z")) self.assertEqual(query_date_range.date_to(), parser.isoparse("2021-08-24T23:59:59.999999Z")) @@ -125,7 +125,7 @@ def setUp(self): self.total_intervals = 5 def test_constructor_initialization(self): - query = QueryDateRangeWithIntervals(None, self.total_intervals, self.team, IntervalType.day, self.now) + query = QueryDateRangeWithIntervals(None, self.total_intervals, self.team, IntervalType.DAY, self.now) self.assertEqual(query.total_intervals, self.total_intervals) def test_determine_time_delta_valid(self): @@ -137,44 +137,44 @@ def test_determine_time_delta_invalid_period(self): QueryDateRangeWithIntervals.determine_time_delta(5, "decade") def test_date_from_day_interval(self): - query = QueryDateRangeWithIntervals(None, 2, self.team, IntervalType.day, self.now) + query = QueryDateRangeWithIntervals(None, 2, self.team, IntervalType.DAY, self.now) self.assertEqual(query.date_from(), parser.isoparse("2021-08-24T00:00:00Z")) def test_date_from_hour_interval(self): - query = QueryDateRangeWithIntervals(None, 48, self.team, IntervalType.hour, self.now) + query = QueryDateRangeWithIntervals(None, 48, self.team, IntervalType.HOUR, self.now) self.assertEqual(query.date_from(), parser.isoparse("2021-08-23T01:00:00Z")) def test_date_from_week_interval_starting_monday(self): self.team.week_start_day = WeekStartDay.MONDAY - query = QueryDateRangeWithIntervals(None, 1, self.team, IntervalType.week, self.now) + query = QueryDateRangeWithIntervals(None, 1, self.team, IntervalType.WEEK, self.now) self.assertEqual(query.date_from(), parser.isoparse("2021-08-23T00:00:00Z")) def test_date_from_week_interval_starting_sunday(self): self.team.week_start_day = WeekStartDay.SUNDAY - query = QueryDateRangeWithIntervals(None, 1, self.team, IntervalType.week, self.now) + query = QueryDateRangeWithIntervals(None, 1, self.team, IntervalType.WEEK, self.now) self.assertEqual(query.date_from(), parser.isoparse("2021-08-22T00:00:00Z")) def test_date_to_day_interval(self): - query = QueryDateRangeWithIntervals(None, 1, self.team, IntervalType.day, self.now) + query = QueryDateRangeWithIntervals(None, 1, self.team, IntervalType.DAY, self.now) self.assertEqual(query.date_to(), parser.isoparse("2021-08-26T00:00:00Z")) def test_date_to_hour_interval(self): - query = QueryDateRangeWithIntervals(None, 1, self.team, IntervalType.hour, self.now) + query = QueryDateRangeWithIntervals(None, 1, self.team, IntervalType.HOUR, self.now) self.assertEqual(query.date_to(), parser.isoparse("2021-08-25T01:00:00Z")) def test_get_start_of_interval_hogql_day_interval(self): - query = QueryDateRangeWithIntervals(None, 1, self.team, IntervalType.day, self.now) + query = QueryDateRangeWithIntervals(None, 1, self.team, IntervalType.DAY, self.now) expected_expr = ast.Call(name="toStartOfDay", args=[ast.Constant(value=query.date_from())]) self.assertEqual(query.get_start_of_interval_hogql(), expected_expr) def test_get_start_of_interval_hogql_hour_interval(self): - query = QueryDateRangeWithIntervals(None, 1, self.team, IntervalType.hour, self.now) + query = QueryDateRangeWithIntervals(None, 1, self.team, IntervalType.HOUR, self.now) expected_expr = ast.Call(name="toStartOfHour", args=[ast.Constant(value=query.date_from())]) self.assertEqual(query.get_start_of_interval_hogql(), expected_expr) def test_get_start_of_interval_hogql_week_interval(self): self.team.week_start_day = WeekStartDay.MONDAY - query = QueryDateRangeWithIntervals(None, 1, self.team, IntervalType.week, self.now) + query = QueryDateRangeWithIntervals(None, 1, self.team, IntervalType.WEEK, self.now) week_mode = WeekStartDay(self.team.week_start_day or 0).clickhouse_mode expected_expr = ast.Call( name="toStartOfWeek", args=[ast.Constant(value=query.date_from()), ast.Constant(value=int(week_mode))] @@ -183,6 +183,6 @@ def test_get_start_of_interval_hogql_week_interval(self): def test_get_start_of_interval_hogql_with_source(self): source_expr = ast.Constant(value="2021-08-25T00:00:00.000Z") - query = QueryDateRangeWithIntervals(None, 1, self.team, IntervalType.day, self.now) + query = QueryDateRangeWithIntervals(None, 1, self.team, IntervalType.DAY, self.now) expected_expr = ast.Call(name="toStartOfDay", args=[source_expr]) self.assertEqual(query.get_start_of_interval_hogql(source=source_expr), expected_expr) diff --git a/posthog/hogql_queries/web_analytics/stats_table.py b/posthog/hogql_queries/web_analytics/stats_table.py index 0b224932b7d76..92521454bdb7f 100644 --- a/posthog/hogql_queries/web_analytics/stats_table.py +++ b/posthog/hogql_queries/web_analytics/stats_table.py @@ -38,12 +38,12 @@ def __init__(self, *args, **kwargs): ) def to_query(self) -> ast.SelectQuery: - if self.query.breakdownBy == WebStatsBreakdown.Page: + if self.query.breakdownBy == WebStatsBreakdown.PAGE: if self.query.includeScrollDepth and self.query.includeBounceRate: return self.to_path_scroll_bounce_query() elif self.query.includeBounceRate: return self.to_path_bounce_query() - if self.query.breakdownBy == WebStatsBreakdown.InitialPage: + if self.query.breakdownBy == WebStatsBreakdown.INITIAL_PAGE: if self.query.includeBounceRate: return self.to_entry_bounce_query() if self._has_session_properties(): @@ -178,7 +178,7 @@ def to_entry_bounce_query(self) -> ast.SelectQuery: return query def to_path_scroll_bounce_query(self) -> ast.SelectQuery: - if self.query.breakdownBy != WebStatsBreakdown.Page: + if self.query.breakdownBy != WebStatsBreakdown.PAGE: raise NotImplementedError("Scroll depth is only supported for page breakdowns") with self.timings.measure("stats_table_bounce_query"): @@ -290,7 +290,7 @@ def to_path_scroll_bounce_query(self) -> ast.SelectQuery: return query def to_path_bounce_query(self) -> ast.SelectQuery: - if self.query.breakdownBy not in [WebStatsBreakdown.InitialPage, WebStatsBreakdown.Page]: + if self.query.breakdownBy not in [WebStatsBreakdown.INITIAL_PAGE, WebStatsBreakdown.PAGE]: raise NotImplementedError("Bounce rate is only supported for page breakdowns") with self.timings.measure("stats_table_scroll_query"): @@ -394,15 +394,15 @@ def _has_session_properties(self) -> bool: return any( get_property_type(p) == "session" for p in self.query.properties + self._test_account_filters ) or self.query.breakdownBy in { - WebStatsBreakdown.InitialChannelType, - WebStatsBreakdown.InitialReferringDomain, - WebStatsBreakdown.InitialUTMSource, - WebStatsBreakdown.InitialUTMCampaign, - WebStatsBreakdown.InitialUTMMedium, - WebStatsBreakdown.InitialUTMTerm, - WebStatsBreakdown.InitialUTMContent, - WebStatsBreakdown.InitialPage, - WebStatsBreakdown.ExitPage, + WebStatsBreakdown.INITIAL_CHANNEL_TYPE, + WebStatsBreakdown.INITIAL_REFERRING_DOMAIN, + WebStatsBreakdown.INITIAL_UTM_SOURCE, + WebStatsBreakdown.INITIAL_UTM_CAMPAIGN, + WebStatsBreakdown.INITIAL_UTM_MEDIUM, + WebStatsBreakdown.INITIAL_UTM_TERM, + WebStatsBreakdown.INITIAL_UTM_CONTENT, + WebStatsBreakdown.INITIAL_PAGE, + WebStatsBreakdown.EXIT_PAGE, } def _session_properties(self) -> ast.Expr: @@ -453,60 +453,60 @@ def calculate(self): def _counts_breakdown_value(self): match self.query.breakdownBy: - case WebStatsBreakdown.Page: + case WebStatsBreakdown.PAGE: return self._apply_path_cleaning(ast.Field(chain=["events", "properties", "$pathname"])) - case WebStatsBreakdown.InitialPage: + case WebStatsBreakdown.INITIAL_PAGE: return self._apply_path_cleaning(ast.Field(chain=["sessions", "$entry_pathname"])) - case WebStatsBreakdown.ExitPage: + case WebStatsBreakdown.EXIT_PAGE: return self._apply_path_cleaning(ast.Field(chain=["sessions", "$exit_pathname"])) - case WebStatsBreakdown.InitialReferringDomain: + case WebStatsBreakdown.INITIAL_REFERRING_DOMAIN: return ast.Field(chain=["sessions", "$entry_referring_domain"]) - case WebStatsBreakdown.InitialUTMSource: + case WebStatsBreakdown.INITIAL_UTM_SOURCE: return ast.Field(chain=["sessions", "$entry_utm_source"]) - case WebStatsBreakdown.InitialUTMCampaign: + case WebStatsBreakdown.INITIAL_UTM_CAMPAIGN: return ast.Field(chain=["sessions", "$entry_utm_campaign"]) - case WebStatsBreakdown.InitialUTMMedium: + case WebStatsBreakdown.INITIAL_UTM_MEDIUM: return ast.Field(chain=["sessions", "$entry_utm_medium"]) - case WebStatsBreakdown.InitialUTMTerm: + case WebStatsBreakdown.INITIAL_UTM_TERM: return ast.Field(chain=["sessions", "$entry_utm_term"]) - case WebStatsBreakdown.InitialUTMContent: + case WebStatsBreakdown.INITIAL_UTM_CONTENT: return ast.Field(chain=["sessions", "$entry_utm_content"]) - case WebStatsBreakdown.InitialChannelType: + case WebStatsBreakdown.INITIAL_CHANNEL_TYPE: return ast.Field(chain=["sessions", "$channel_type"]) - case WebStatsBreakdown.Browser: + case WebStatsBreakdown.BROWSER: return ast.Field(chain=["properties", "$browser"]) case WebStatsBreakdown.OS: return ast.Field(chain=["properties", "$os"]) - case WebStatsBreakdown.DeviceType: + case WebStatsBreakdown.DEVICE_TYPE: return ast.Field(chain=["properties", "$device_type"]) - case WebStatsBreakdown.Country: + case WebStatsBreakdown.COUNTRY: return ast.Field(chain=["properties", "$geoip_country_code"]) - case WebStatsBreakdown.Region: + case WebStatsBreakdown.REGION: return parse_expr( "tuple(properties.$geoip_country_code, properties.$geoip_subdivision_1_code, properties.$geoip_subdivision_1_name)" ) - case WebStatsBreakdown.City: + case WebStatsBreakdown.CITY: return parse_expr("tuple(properties.$geoip_country_code, properties.$geoip_city_name)") case _: raise NotImplementedError("Breakdown not implemented") def where_breakdown(self): match self.query.breakdownBy: - case WebStatsBreakdown.Region: + case WebStatsBreakdown.REGION: return parse_expr("tupleElement(breakdown_value, 2) IS NOT NULL") - case WebStatsBreakdown.City: + case WebStatsBreakdown.CITY: return parse_expr("tupleElement(breakdown_value, 2) IS NOT NULL") - case WebStatsBreakdown.InitialChannelType: + case WebStatsBreakdown.INITIAL_CHANNEL_TYPE: return parse_expr("TRUE") # actually show null values - case WebStatsBreakdown.InitialUTMSource: + case WebStatsBreakdown.INITIAL_UTM_SOURCE: return parse_expr("TRUE") # actually show null values - case WebStatsBreakdown.InitialUTMCampaign: + case WebStatsBreakdown.INITIAL_UTM_CAMPAIGN: return parse_expr("TRUE") # actually show null values - case WebStatsBreakdown.InitialUTMMedium: + case WebStatsBreakdown.INITIAL_UTM_MEDIUM: return parse_expr("TRUE") # actually show null values - case WebStatsBreakdown.InitialUTMTerm: + case WebStatsBreakdown.INITIAL_UTM_TERM: return parse_expr("TRUE") # actually show null values - case WebStatsBreakdown.InitialUTMContent: + case WebStatsBreakdown.INITIAL_UTM_CONTENT: return parse_expr("TRUE") # actually show null values case _: return parse_expr("breakdown_value IS NOT NULL") diff --git a/posthog/hogql_queries/web_analytics/stats_table_legacy.py b/posthog/hogql_queries/web_analytics/stats_table_legacy.py index edb72c39ae92e..5cb6a2a3c0889 100644 --- a/posthog/hogql_queries/web_analytics/stats_table_legacy.py +++ b/posthog/hogql_queries/web_analytics/stats_table_legacy.py @@ -71,7 +71,7 @@ def _scroll_depth_subquery(self): def to_query(self) -> ast.SelectQuery: # special case for channel, as some hogql features to use the general code are still being worked on - if self.query.breakdownBy == WebStatsBreakdown.InitialChannelType: + if self.query.breakdownBy == WebStatsBreakdown.INITIAL_CHANNEL_TYPE: query = self.to_channel_query() elif self.query.includeScrollDepth: query = parse_select( @@ -193,51 +193,51 @@ def calculate(self): def counts_breakdown(self): match self.query.breakdownBy: - case WebStatsBreakdown.Page: + case WebStatsBreakdown.PAGE: return self._apply_path_cleaning(ast.Field(chain=["properties", "$pathname"])) - case WebStatsBreakdown.InitialChannelType: + case WebStatsBreakdown.INITIAL_CHANNEL_TYPE: raise NotImplementedError("Breakdown InitialChannelType not implemented") - case WebStatsBreakdown.InitialPage: + case WebStatsBreakdown.INITIAL_PAGE: return self._apply_path_cleaning(ast.Field(chain=["person", "properties", "$initial_pathname"])) - case WebStatsBreakdown.InitialReferringDomain: + case WebStatsBreakdown.INITIAL_REFERRING_DOMAIN: return ast.Field(chain=["person", "properties", "$initial_referring_domain"]) - case WebStatsBreakdown.InitialUTMSource: + case WebStatsBreakdown.INITIAL_UTM_SOURCE: return ast.Field(chain=["person", "properties", "$initial_utm_source"]) - case WebStatsBreakdown.InitialUTMCampaign: + case WebStatsBreakdown.INITIAL_UTM_CAMPAIGN: return ast.Field(chain=["person", "properties", "$initial_utm_campaign"]) - case WebStatsBreakdown.InitialUTMMedium: + case WebStatsBreakdown.INITIAL_UTM_MEDIUM: return ast.Field(chain=["person", "properties", "$initial_utm_medium"]) - case WebStatsBreakdown.InitialUTMTerm: + case WebStatsBreakdown.INITIAL_UTM_TERM: return ast.Field(chain=["person", "properties", "$initial_utm_term"]) - case WebStatsBreakdown.InitialUTMContent: + case WebStatsBreakdown.INITIAL_UTM_CONTENT: return ast.Field(chain=["person", "properties", "$initial_utm_content"]) - case WebStatsBreakdown.Browser: + case WebStatsBreakdown.BROWSER: return ast.Field(chain=["properties", "$browser"]) case WebStatsBreakdown.OS: return ast.Field(chain=["properties", "$os"]) - case WebStatsBreakdown.DeviceType: + case WebStatsBreakdown.DEVICE_TYPE: return ast.Field(chain=["properties", "$device_type"]) - case WebStatsBreakdown.Country: + case WebStatsBreakdown.COUNTRY: return ast.Field(chain=["properties", "$geoip_country_code"]) - case WebStatsBreakdown.Region: + case WebStatsBreakdown.REGION: return parse_expr( "tuple(properties.$geoip_country_code, properties.$geoip_subdivision_1_code, properties.$geoip_subdivision_1_name)" ) - case WebStatsBreakdown.City: + case WebStatsBreakdown.CITY: return parse_expr("tuple(properties.$geoip_country_code, properties.$geoip_city_name)") case _: raise NotImplementedError("Breakdown not implemented") def bounce_breakdown(self): match self.query.breakdownBy: - case WebStatsBreakdown.Page: + case WebStatsBreakdown.PAGE: # use initial pathname for bounce rate return self._apply_path_cleaning( ast.Call(name="any", args=[ast.Field(chain=["person", "properties", "$initial_pathname"])]) ) - case WebStatsBreakdown.InitialChannelType: + case WebStatsBreakdown.INITIAL_CHANNEL_TYPE: raise NotImplementedError("Breakdown InitialChannelType not implemented") - case WebStatsBreakdown.InitialPage: + case WebStatsBreakdown.INITIAL_PAGE: return self._apply_path_cleaning( ast.Call(name="any", args=[ast.Field(chain=["person", "properties", "$initial_pathname"])]) ) @@ -246,21 +246,21 @@ def bounce_breakdown(self): def where_breakdown(self): match self.query.breakdownBy: - case WebStatsBreakdown.Region: + case WebStatsBreakdown.REGION: return parse_expr('tupleElement("context.columns.breakdown_value", 2) IS NOT NULL') - case WebStatsBreakdown.City: + case WebStatsBreakdown.CITY: return parse_expr('tupleElement("context.columns.breakdown_value", 2) IS NOT NULL') - case WebStatsBreakdown.InitialChannelType: + case WebStatsBreakdown.INITIAL_CHANNEL_TYPE: return parse_expr("TRUE") # actually show null values - case WebStatsBreakdown.InitialUTMSource: + case WebStatsBreakdown.INITIAL_UTM_SOURCE: return parse_expr("TRUE") # actually show null values - case WebStatsBreakdown.InitialUTMCampaign: + case WebStatsBreakdown.INITIAL_UTM_CAMPAIGN: return parse_expr("TRUE") # actually show null values - case WebStatsBreakdown.InitialUTMMedium: + case WebStatsBreakdown.INITIAL_UTM_MEDIUM: return parse_expr("TRUE") # actually show null values - case WebStatsBreakdown.InitialUTMTerm: + case WebStatsBreakdown.INITIAL_UTM_TERM: return parse_expr("TRUE") # actually show null values - case WebStatsBreakdown.InitialUTMContent: + case WebStatsBreakdown.INITIAL_UTM_CONTENT: return parse_expr("TRUE") # actually show null values case _: return parse_expr('"context.columns.breakdown_value" IS NOT NULL') diff --git a/posthog/hogql_queries/web_analytics/test/test_web_analytics_query_runner.py b/posthog/hogql_queries/web_analytics/test/test_web_analytics_query_runner.py index 3ea217606522c..87763dc288b61 100644 --- a/posthog/hogql_queries/web_analytics/test/test_web_analytics_query_runner.py +++ b/posthog/hogql_queries/web_analytics/test/test_web_analytics_query_runner.py @@ -48,7 +48,7 @@ def _create_events(self, data, event="$pageview"): ) return person_result - def _create_web_stats_table_query(self, date_from, date_to, properties, breakdown_by=WebStatsBreakdown.Page): + def _create_web_stats_table_query(self, date_from, date_to, properties, breakdown_by=WebStatsBreakdown.PAGE): query = WebStatsTableQuery( dateRange=DateRange(date_from=date_from, date_to=date_to), properties=properties, breakdownBy=breakdown_by ) @@ -63,8 +63,8 @@ def _create__web_overview_query(self, date_from, date_to, properties): def test_sample_rate_cache_key_is_same_across_subclasses(self): properties: list[Union[EventPropertyFilter, PersonPropertyFilter]] = [ - EventPropertyFilter(key="$current_url", value="/a", operator=PropertyOperator.is_not), - PersonPropertyFilter(key="$initial_utm_source", value="google", operator=PropertyOperator.is_not), + EventPropertyFilter(key="$current_url", value="/a", operator=PropertyOperator.IS_NOT), + PersonPropertyFilter(key="$initial_utm_source", value="google", operator=PropertyOperator.IS_NOT), ] date_from = "2023-12-08" date_to = "2023-12-15" @@ -76,10 +76,10 @@ def test_sample_rate_cache_key_is_same_across_subclasses(self): def test_sample_rate_cache_key_is_same_with_different_properties(self): properties_a: list[Union[EventPropertyFilter, PersonPropertyFilter]] = [ - EventPropertyFilter(key="$current_url", value="/a", operator=PropertyOperator.is_not), + EventPropertyFilter(key="$current_url", value="/a", operator=PropertyOperator.IS_NOT), ] properties_b: list[Union[EventPropertyFilter, PersonPropertyFilter]] = [ - EventPropertyFilter(key="$current_url", value="/b", operator=PropertyOperator.is_not), + EventPropertyFilter(key="$current_url", value="/b", operator=PropertyOperator.IS_NOT), ] date_from = "2023-12-08" date_to = "2023-12-15" @@ -91,7 +91,7 @@ def test_sample_rate_cache_key_is_same_with_different_properties(self): def test_sample_rate_cache_key_changes_with_date_range(self): properties: list[Union[EventPropertyFilter, PersonPropertyFilter]] = [ - EventPropertyFilter(key="$current_url", value="/a", operator=PropertyOperator.is_not), + EventPropertyFilter(key="$current_url", value="/a", operator=PropertyOperator.IS_NOT), ] date_from_a = "2023-12-08" date_from_b = "2023-12-09" diff --git a/posthog/hogql_queries/web_analytics/test/test_web_stats_table.py b/posthog/hogql_queries/web_analytics/test/test_web_stats_table.py index b753abdad0f64..c78010569a60b 100644 --- a/posthog/hogql_queries/web_analytics/test/test_web_stats_table.py +++ b/posthog/hogql_queries/web_analytics/test/test_web_stats_table.py @@ -93,7 +93,7 @@ def _run_web_stats_table_query( self, date_from, date_to, - breakdown_by=WebStatsBreakdown.Page, + breakdown_by=WebStatsBreakdown.PAGE, limit=None, path_cleaning_filters=None, use_sessions_table=True, @@ -192,7 +192,7 @@ def test_breakdown_channel_type_doesnt_throw(self, use_sessions_table): results = self._run_web_stats_table_query( "2023-12-01", "2023-12-03", - breakdown_by=WebStatsBreakdown.InitialChannelType, + breakdown_by=WebStatsBreakdown.INITIAL_CHANNEL_TYPE, use_sessions_table=use_sessions_table, ).results @@ -279,7 +279,7 @@ def test_scroll_depth_bounce_rate_one_user(self): "all", "2023-12-15", use_sessions_table=True, - breakdown_by=WebStatsBreakdown.Page, + breakdown_by=WebStatsBreakdown.PAGE, include_scroll_depth=True, include_bounce_rate=True, ).results @@ -322,7 +322,7 @@ def test_scroll_depth_bounce_rate(self): "all", "2023-12-15", use_sessions_table=True, - breakdown_by=WebStatsBreakdown.Page, + breakdown_by=WebStatsBreakdown.PAGE, include_scroll_depth=True, include_bounce_rate=True, ).results @@ -365,10 +365,10 @@ def test_scroll_depth_bounce_rate_with_filter(self): "all", "2023-12-15", use_sessions_table=True, - breakdown_by=WebStatsBreakdown.Page, + breakdown_by=WebStatsBreakdown.PAGE, include_scroll_depth=True, include_bounce_rate=True, - properties=[EventPropertyFilter(key="$pathname", operator=PropertyOperator.exact, value="/a")], + properties=[EventPropertyFilter(key="$pathname", operator=PropertyOperator.EXACT, value="/a")], ).results self.assertEqual( @@ -392,7 +392,7 @@ def test_scroll_depth_bounce_rate_path_cleaning(self): "all", "2023-12-15", use_sessions_table=True, - breakdown_by=WebStatsBreakdown.Page, + breakdown_by=WebStatsBreakdown.PAGE, include_scroll_depth=True, include_bounce_rate=True, path_cleaning_filters=[ @@ -425,7 +425,7 @@ def test_bounce_rate_one_user(self): "all", "2023-12-15", use_sessions_table=True, - breakdown_by=WebStatsBreakdown.Page, + breakdown_by=WebStatsBreakdown.PAGE, include_bounce_rate=True, ).results @@ -467,7 +467,7 @@ def test_bounce_rate(self): "all", "2023-12-15", use_sessions_table=True, - breakdown_by=WebStatsBreakdown.Page, + breakdown_by=WebStatsBreakdown.PAGE, include_bounce_rate=True, ).results @@ -509,9 +509,9 @@ def test_bounce_rate_with_property(self): "all", "2023-12-15", use_sessions_table=True, - breakdown_by=WebStatsBreakdown.Page, + breakdown_by=WebStatsBreakdown.PAGE, include_bounce_rate=True, - properties=[EventPropertyFilter(key="$pathname", operator=PropertyOperator.exact, value="/a")], + properties=[EventPropertyFilter(key="$pathname", operator=PropertyOperator.EXACT, value="/a")], ).results self.assertEqual( @@ -535,7 +535,7 @@ def test_bounce_rate_path_cleaning(self): "all", "2023-12-15", use_sessions_table=True, - breakdown_by=WebStatsBreakdown.Page, + breakdown_by=WebStatsBreakdown.PAGE, include_bounce_rate=True, path_cleaning_filters=[ {"regex": "\\/a\\/\\d+", "alias": "/a/:id"}, @@ -567,7 +567,7 @@ def test_entry_bounce_rate_one_user(self): "all", "2023-12-15", use_sessions_table=True, - breakdown_by=WebStatsBreakdown.InitialPage, + breakdown_by=WebStatsBreakdown.INITIAL_PAGE, include_bounce_rate=True, ).results @@ -607,7 +607,7 @@ def test_entry_bounce_rate(self): "all", "2023-12-15", use_sessions_table=True, - breakdown_by=WebStatsBreakdown.InitialPage, + breakdown_by=WebStatsBreakdown.INITIAL_PAGE, include_bounce_rate=True, ).results @@ -647,9 +647,9 @@ def test_entry_bounce_rate_with_property(self): "all", "2023-12-15", use_sessions_table=True, - breakdown_by=WebStatsBreakdown.InitialPage, + breakdown_by=WebStatsBreakdown.INITIAL_PAGE, include_bounce_rate=True, - properties=[EventPropertyFilter(key="$pathname", operator=PropertyOperator.exact, value="/a")], + properties=[EventPropertyFilter(key="$pathname", operator=PropertyOperator.EXACT, value="/a")], ).results self.assertEqual( @@ -673,7 +673,7 @@ def test_entry_bounce_rate_path_cleaning(self): "all", "2023-12-15", use_sessions_table=True, - breakdown_by=WebStatsBreakdown.InitialPage, + breakdown_by=WebStatsBreakdown.INITIAL_PAGE, include_bounce_rate=True, path_cleaning_filters=[ {"regex": "\\/a\\/\\d+", "alias": "/a/:id"}, diff --git a/posthog/jwt.py b/posthog/jwt.py index 62710d9159b86..ead4196aa4730 100644 --- a/posthog/jwt.py +++ b/posthog/jwt.py @@ -10,7 +10,7 @@ class PosthogJwtAudience(Enum): UNSUBSCRIBE = "posthog:unsubscribe" EXPORTED_ASSET = "posthog:exported_asset" IMPERSONATED_USER = "posthog:impersonted_user" # This is used by background jobs on behalf of the user e.g. exports - LIVE_EVENTS = "posthog:live_events" + LIVESTREAM = "posthog:livestream" def encode_jwt(payload: dict, expiry_delta: timedelta, audience: PosthogJwtAudience) -> str: diff --git a/posthog/management/commands/compare_hogql_insights.py b/posthog/management/commands/compare_hogql_insights.py index 44bab4f6e7127..9a49af107e063 100644 --- a/posthog/management/commands/compare_hogql_insights.py +++ b/posthog/management/commands/compare_hogql_insights.py @@ -38,7 +38,9 @@ def handle(self, *args, **options): if event.get("math") in ("median", "p90", "p95", "p99"): event["math"] = "sum" try: - print("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++") # noqa: T201 + print( # noqa: T201 + "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++" + ) insight_type = insight.filters.get("insight") print( # noqa: T201 f"Checking {insight_type} Insight {insight.id} {insight.short_id} - {insight.name} " @@ -58,7 +60,7 @@ def handle(self, *args, **options): continue try: query = filter_to_query(insight.filters) - modifiers = HogQLQueryModifiers(materializationMode=MaterializationMode.legacy_null_as_string) + modifiers = HogQLQueryModifiers(materializationMode=MaterializationMode.LEGACY_NULL_AS_STRING) query_runner = get_query_runner(query, insight.team, modifiers=modifiers) hogql_results = cast(HogQLQueryResponse, query_runner.calculate()).results or [] except Exception as e: diff --git a/posthog/management/commands/start_temporal_worker.py b/posthog/management/commands/start_temporal_worker.py index 3fb2e0444e87f..1b79e594323e7 100644 --- a/posthog/management/commands/start_temporal_worker.py +++ b/posthog/management/commands/start_temporal_worker.py @@ -92,7 +92,6 @@ def handle(self, *args, **options): logging.info(f"Starting Temporal Worker with options: {options}") structlog.reset_defaults() - metrics_port = int(options["metrics_port"]) asyncio.run( diff --git a/posthog/models/__init__.py b/posthog/models/__init__.py index d0f9dcf893c80..389d573cb7e7b 100644 --- a/posthog/models/__init__.py +++ b/posthog/models/__init__.py @@ -40,6 +40,7 @@ from .filters import Filter, RetentionFilter from .group import Group from .group_type_mapping import GroupTypeMapping +from .hog_functions import HogFunction from .insight import Insight, InsightViewed from .insight_caching_state import InsightCachingState from .instance_setting import InstanceSetting @@ -103,6 +104,7 @@ "Filter", "Group", "GroupTypeMapping", + "HogFunction", "Insight", "InsightCachingState", "InsightViewed", diff --git a/posthog/models/team/team.py b/posthog/models/team/team.py index 382431171680e..e06104ce1c2a6 100644 --- a/posthog/models/team/team.py +++ b/posthog/models/team/team.py @@ -302,31 +302,31 @@ def default_modifiers(self) -> dict: @property def person_on_events_mode(self) -> PersonsOnEventsMode: if self._person_on_events_person_id_override_properties_on_events: - tag_queries(person_on_events_mode=PersonsOnEventsMode.person_id_override_properties_on_events) - return PersonsOnEventsMode.person_id_override_properties_on_events + tag_queries(person_on_events_mode=PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS) + return PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS if self._person_on_events_person_id_no_override_properties_on_events: # also tag person_on_events_enabled for legacy compatibility tag_queries( person_on_events_enabled=True, - person_on_events_mode=PersonsOnEventsMode.person_id_no_override_properties_on_events, + person_on_events_mode=PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS, ) - return PersonsOnEventsMode.person_id_no_override_properties_on_events + return PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS if self._person_on_events_person_id_override_properties_joined: tag_queries( person_on_events_enabled=True, - person_on_events_mode=PersonsOnEventsMode.person_id_override_properties_joined, + person_on_events_mode=PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_JOINED, ) - return PersonsOnEventsMode.person_id_override_properties_joined + return PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_JOINED - return PersonsOnEventsMode.disabled + return PersonsOnEventsMode.DISABLED # KLUDGE: DO NOT REFERENCE IN THE BACKEND! # Keeping this property for now only to be used by the frontend in certain cases @property def person_on_events_querying_enabled(self) -> bool: - return self.person_on_events_mode != PersonsOnEventsMode.disabled + return self.person_on_events_mode != PersonsOnEventsMode.DISABLED @property def _person_on_events_person_id_no_override_properties_on_events(self) -> bool: diff --git a/posthog/permissions.py b/posthog/permissions.py index db5c48d92f25b..72fd657a9ad4f 100644 --- a/posthog/permissions.py +++ b/posthog/permissions.py @@ -5,6 +5,7 @@ from django.core.exceptions import ImproperlyConfigured from django.db.models import Model from django.views import View +import posthoganalytics from rest_framework.exceptions import NotFound, PermissionDenied from rest_framework.permissions import SAFE_METHODS, BasePermission, IsAdminUser from rest_framework.request import Request @@ -404,3 +405,39 @@ def get_scope_object(self, request, view) -> APIScopeObjectOrNotSupported: raise ImproperlyConfigured("APIScopePermission requires the view to define the scope_object attribute.") return view.scope_object + + +class PostHogFeatureFlagPermission(BasePermission): + def has_permission(self, request, view) -> bool: + user = cast(User, request.user) + organization = get_organization_from_view(view) + flag = getattr(view, "posthog_feature_flag", None) + + config = {} + + if not flag: + raise ImproperlyConfigured( + "PostHogFeatureFlagPermission requires the view to define the posthog_feature_flag attribute." + ) + + if isinstance(flag, str): + config[flag] = ["*"] + else: + config = flag + + for required_flag, actions in config.items(): + if "*" in actions or view.action in actions: + org_id = str(organization.id) + + enabled = posthoganalytics.feature_enabled( + required_flag, + user.distinct_id, + groups={"organization": org_id}, + group_properties={"organization": {"id": org_id}}, + only_evaluate_locally=False, + send_feature_flag_events=False, + ) + + return enabled or False + + return True diff --git a/posthog/queries/breakdown_props.py b/posthog/queries/breakdown_props.py index 767985e261109..23f4b0d51ddc4 100644 --- a/posthog/queries/breakdown_props.py +++ b/posthog/queries/breakdown_props.py @@ -86,7 +86,7 @@ def get_breakdown_prop_values( sessions_join_params: dict = {} null_person_filter = ( - f"AND notEmpty(e.person_id)" if team.person_on_events_mode != PersonsOnEventsMode.disabled else "" + f"AND notEmpty(e.person_id)" if team.person_on_events_mode != PersonsOnEventsMode.DISABLED else "" ) if person_properties_mode == PersonPropertiesMode.DIRECT_ON_EVENTS: @@ -277,12 +277,14 @@ def _to_value_expression( table="events" if direct_on_events else "groups", property_name=cast(str, breakdown), var="%(key)s", - column=f"group{breakdown_group_type_index}_properties" - if direct_on_events - else f"group_properties_{breakdown_group_type_index}", - materialised_table_column=f"group{breakdown_group_type_index}_properties" - if direct_on_events - else "group_properties", + column=( + f"group{breakdown_group_type_index}_properties" + if direct_on_events + else f"group_properties_{breakdown_group_type_index}" + ), + materialised_table_column=( + f"group{breakdown_group_type_index}_properties" if direct_on_events else "group_properties" + ), ) elif breakdown_type == "hogql": from posthog.hogql.hogql import translate_hogql diff --git a/posthog/queries/event_query/event_query.py b/posthog/queries/event_query/event_query.py index cc514c676f817..d8816634d6ac1 100644 --- a/posthog/queries/event_query/event_query.py +++ b/posthog/queries/event_query/event_query.py @@ -64,7 +64,7 @@ def __init__( extra_event_properties: Optional[list[PropertyName]] = None, extra_person_fields: Optional[list[ColumnName]] = None, override_aggregate_users_by_distinct_id: Optional[bool] = None, - person_on_events_mode: PersonsOnEventsMode = PersonsOnEventsMode.disabled, + person_on_events_mode: PersonsOnEventsMode = PersonsOnEventsMode.DISABLED, **kwargs, ) -> None: if extra_person_fields is None: @@ -126,9 +126,9 @@ def _determine_should_join_distinct_ids(self) -> None: pass def _get_person_id_alias(self, person_on_events_mode) -> str: - if person_on_events_mode == PersonsOnEventsMode.person_id_override_properties_on_events: + if person_on_events_mode == PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS: return f"if(notEmpty({self.PERSON_ID_OVERRIDES_TABLE_ALIAS}.distinct_id), {self.PERSON_ID_OVERRIDES_TABLE_ALIAS}.person_id, {self.EVENT_TABLE_ALIAS}.person_id)" - elif person_on_events_mode == PersonsOnEventsMode.person_id_no_override_properties_on_events: + elif person_on_events_mode == PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS: return f"{self.EVENT_TABLE_ALIAS}.person_id" return f"{self.DISTINCT_ID_TABLE_ALIAS}.person_id" @@ -137,7 +137,7 @@ def _get_person_ids_query(self, *, relevant_events_conditions: str = "") -> str: if not self._should_join_distinct_ids: return "" - if self._person_on_events_mode == PersonsOnEventsMode.person_id_override_properties_on_events: + if self._person_on_events_mode == PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS: return PERSON_DISTINCT_ID_OVERRIDES_JOIN_SQL.format( person_overrides_table_alias=self.PERSON_ID_OVERRIDES_TABLE_ALIAS, event_table_alias=self.EVENT_TABLE_ALIAS, diff --git a/posthog/queries/foss_cohort_query.py b/posthog/queries/foss_cohort_query.py index e801008c44726..d4925856afd94 100644 --- a/posthog/queries/foss_cohort_query.py +++ b/posthog/queries/foss_cohort_query.py @@ -191,9 +191,11 @@ def _unwrap(property_group: PropertyGroup, negate_group: bool = False) -> Proper ) else: return PropertyGroup( - type=PropertyOperatorType.AND - if property_group.type == PropertyOperatorType.OR - else PropertyOperatorType.OR, + type=( + PropertyOperatorType.AND + if property_group.type == PropertyOperatorType.OR + else PropertyOperatorType.OR + ), values=[_unwrap(v, True) for v in cast(list[PropertyGroup], property_group.values)], ) @@ -246,9 +248,11 @@ def _unwrap(property_group: PropertyGroup, negate_group: bool = False) -> Proper return PropertyGroup(type=property_group.type, values=new_property_group_list) else: return PropertyGroup( - type=PropertyOperatorType.AND - if property_group.type == PropertyOperatorType.OR - else PropertyOperatorType.OR, + type=( + PropertyOperatorType.AND + if property_group.type == PropertyOperatorType.OR + else PropertyOperatorType.OR + ), values=new_property_group_list, ) @@ -306,7 +310,7 @@ def _build_sources(self, subq: list[tuple[str, str]]) -> tuple[str, str]: fields = f"{subq_alias}.person_id" elif prev_alias: # can't join without a previous alias if subq_alias == self.PERSON_TABLE_ALIAS and self.should_pushdown_persons: - if self._person_on_events_mode == PersonsOnEventsMode.person_id_no_override_properties_on_events: + if self._person_on_events_mode == PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS: # when using person-on-events, instead of inner join, we filter inside # the event query itself continue @@ -337,11 +341,11 @@ def _get_behavior_subquery(self) -> tuple[str, dict[str, Any], str]: query, params = "", {} if self._should_join_behavioral_query: _fields = [ - f"{self.DISTINCT_ID_TABLE_ALIAS if self._person_on_events_mode == PersonsOnEventsMode.disabled else self.EVENT_TABLE_ALIAS}.person_id AS person_id" + f"{self.DISTINCT_ID_TABLE_ALIAS if self._person_on_events_mode == PersonsOnEventsMode.DISABLED else self.EVENT_TABLE_ALIAS}.person_id AS person_id" ] _fields.extend(self._fields) - if self.should_pushdown_persons and self._person_on_events_mode != PersonsOnEventsMode.disabled: + if self.should_pushdown_persons and self._person_on_events_mode != PersonsOnEventsMode.DISABLED: person_prop_query, person_prop_params = self._get_prop_groups( self._inner_property_groups, person_properties_mode=PersonPropertiesMode.DIRECT_ON_EVENTS, @@ -557,7 +561,7 @@ def get_performed_event_multiple(self, prop: Property, prepend: str, idx: int) - def _determine_should_join_distinct_ids(self) -> None: self._should_join_distinct_ids = ( - self._person_on_events_mode != PersonsOnEventsMode.person_id_no_override_properties_on_events + self._person_on_events_mode != PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS ) def _determine_should_join_persons(self) -> None: diff --git a/posthog/queries/funnels/base.py b/posthog/queries/funnels/base.py index a6de14b050cfa..30265cace41e3 100644 --- a/posthog/queries/funnels/base.py +++ b/posthog/queries/funnels/base.py @@ -228,9 +228,11 @@ def _format_single_funnel(self, results, with_breakdown=False): # breakdown_value will return the underlying id if different from display ready value (ex: cohort id) serialized_result.update( { - "breakdown": get_breakdown_cohort_name(breakdown_value) - if self._filter.breakdown_type == "cohort" - else breakdown_value, + "breakdown": ( + get_breakdown_cohort_name(breakdown_value) + if self._filter.breakdown_type == "cohort" + else breakdown_value + ), "breakdown_value": breakdown_value, } ) @@ -728,7 +730,7 @@ def _get_breakdown_select_prop(self) -> tuple[str, dict[str, Any]]: self.params.update({"breakdown": self._filter.breakdown}) if self._filter.breakdown_type == "person": - if self._team.person_on_events_mode != PersonsOnEventsMode.disabled: + if self._team.person_on_events_mode != PersonsOnEventsMode.DISABLED: basic_prop_selector, basic_prop_params = get_single_or_multi_property_string_expr( self._filter.breakdown, table="events", @@ -758,7 +760,7 @@ def _get_breakdown_select_prop(self) -> tuple[str, dict[str, Any]]: # :TRICKY: We only support string breakdown for group properties assert isinstance(self._filter.breakdown, str) - if self._team.person_on_events_mode != PersonsOnEventsMode.disabled and groups_on_events_querying_enabled(): + if self._team.person_on_events_mode != PersonsOnEventsMode.DISABLED and groups_on_events_querying_enabled(): properties_field = f"group{self._filter.breakdown_group_type_index}_properties" expression, _ = get_property_string_expr( table="events", diff --git a/posthog/queries/funnels/funnel_event_query.py b/posthog/queries/funnels/funnel_event_query.py index a814f9cb54801..a99f456a33eea 100644 --- a/posthog/queries/funnels/funnel_event_query.py +++ b/posthog/queries/funnels/funnel_event_query.py @@ -49,7 +49,7 @@ def get_query( _fields += [f"{self.EVENT_TABLE_ALIAS}.{field} AS {field}" for field in self._extra_fields] - if self._person_on_events_mode != PersonsOnEventsMode.disabled: + if self._person_on_events_mode != PersonsOnEventsMode.DISABLED: _fields += [f"{self._person_id_alias} as person_id"] _fields.extend( @@ -95,7 +95,7 @@ def get_query( null_person_filter = ( f"AND notEmpty({self.EVENT_TABLE_ALIAS}.person_id)" - if self._person_on_events_mode != PersonsOnEventsMode.disabled + if self._person_on_events_mode != PersonsOnEventsMode.DISABLED else "" ) @@ -131,9 +131,9 @@ def _determine_should_join_distinct_ids(self) -> None: ) is_using_cohort_propertes = self._column_optimizer.is_using_cohort_propertes - if self._person_on_events_mode == PersonsOnEventsMode.person_id_override_properties_on_events: + if self._person_on_events_mode == PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS: self._should_join_distinct_ids = True - elif self._person_on_events_mode == PersonsOnEventsMode.person_id_no_override_properties_on_events or ( + elif self._person_on_events_mode == PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS or ( non_person_id_aggregation and not is_using_cohort_propertes ): self._should_join_distinct_ids = False @@ -142,7 +142,7 @@ def _determine_should_join_distinct_ids(self) -> None: def _determine_should_join_persons(self) -> None: EventQuery._determine_should_join_persons(self) - if self._person_on_events_mode != PersonsOnEventsMode.disabled: + if self._person_on_events_mode != PersonsOnEventsMode.DISABLED: self._should_join_persons = False def _get_entity_query(self, entities=None, entity_name="events") -> tuple[str, dict[str, Any]]: diff --git a/posthog/queries/groups_join_query/groups_join_query.py b/posthog/queries/groups_join_query/groups_join_query.py index 6499d39ce1e94..128398584a352 100644 --- a/posthog/queries/groups_join_query/groups_join_query.py +++ b/posthog/queries/groups_join_query/groups_join_query.py @@ -23,7 +23,7 @@ def __init__( team_id: int, column_optimizer: Optional[ColumnOptimizer] = None, join_key: Optional[str] = None, - person_on_events_mode: PersonsOnEventsMode = PersonsOnEventsMode.disabled, + person_on_events_mode: PersonsOnEventsMode = PersonsOnEventsMode.DISABLED, ) -> None: self._filter = filter self._team_id = team_id diff --git a/posthog/queries/paths/paths_event_query.py b/posthog/queries/paths/paths_event_query.py index 31241cea64919..6380c43aae72c 100644 --- a/posthog/queries/paths/paths_event_query.py +++ b/posthog/queries/paths/paths_event_query.py @@ -116,7 +116,7 @@ def get_query(self) -> tuple[str, dict[str, Any]]: null_person_filter = ( f"AND notEmpty({self.EVENT_TABLE_ALIAS}.person_id)" - if self._person_on_events_mode != PersonsOnEventsMode.disabled + if self._person_on_events_mode != PersonsOnEventsMode.DISABLED else "" ) @@ -141,14 +141,14 @@ def get_query(self) -> tuple[str, dict[str, Any]]: return query, self.params def _determine_should_join_distinct_ids(self) -> None: - if self._person_on_events_mode == PersonsOnEventsMode.person_id_no_override_properties_on_events: + if self._person_on_events_mode == PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS: self._should_join_distinct_ids = False else: self._should_join_distinct_ids = True def _determine_should_join_persons(self) -> None: EventQuery._determine_should_join_persons(self) - if self._person_on_events_mode != PersonsOnEventsMode.disabled: + if self._person_on_events_mode != PersonsOnEventsMode.DISABLED: self._should_join_persons = False def _get_grouping_fields(self) -> tuple[list[str], dict[str, Any]]: diff --git a/posthog/queries/retention/retention.py b/posthog/queries/retention/retention.py index d3b9f43ca5c60..5d0779071ffe4 100644 --- a/posthog/queries/retention/retention.py +++ b/posthog/queries/retention/retention.py @@ -166,7 +166,7 @@ def build_returning_event_query( filter: RetentionFilter, team: Team, aggregate_users_by_distinct_id: Optional[bool] = None, - person_on_events_mode: PersonsOnEventsMode = PersonsOnEventsMode.disabled, + person_on_events_mode: PersonsOnEventsMode = PersonsOnEventsMode.DISABLED, retention_events_query=RetentionEventsQuery, ) -> tuple[str, dict[str, Any]]: returning_event_query_templated, returning_event_params = retention_events_query( @@ -184,7 +184,7 @@ def build_target_event_query( filter: RetentionFilter, team: Team, aggregate_users_by_distinct_id: Optional[bool] = None, - person_on_events_mode: PersonsOnEventsMode = PersonsOnEventsMode.disabled, + person_on_events_mode: PersonsOnEventsMode = PersonsOnEventsMode.DISABLED, retention_events_query=RetentionEventsQuery, ) -> tuple[str, dict[str, Any]]: target_event_query_templated, target_event_params = retention_events_query( diff --git a/posthog/queries/retention/retention_events_query.py b/posthog/queries/retention/retention_events_query.py index 9e64b758be6e8..ed9b45d47b584 100644 --- a/posthog/queries/retention/retention_events_query.py +++ b/posthog/queries/retention/retention_events_query.py @@ -27,7 +27,7 @@ def __init__( event_query_type: RetentionQueryType, team: Team, aggregate_users_by_distinct_id: Optional[bool] = None, - person_on_events_mode: PersonsOnEventsMode = PersonsOnEventsMode.disabled, + person_on_events_mode: PersonsOnEventsMode = PersonsOnEventsMode.DISABLED, ): self._event_query_type = event_query_type super().__init__( @@ -56,14 +56,14 @@ def get_query(self) -> tuple[str, dict[str, Any]]: materalised_table_column = "properties" if breakdown_type == "person": - table = "person" if self._person_on_events_mode == PersonsOnEventsMode.disabled else "events" + table = "person" if self._person_on_events_mode == PersonsOnEventsMode.DISABLED else "events" column = ( "person_props" - if self._person_on_events_mode == PersonsOnEventsMode.disabled + if self._person_on_events_mode == PersonsOnEventsMode.DISABLED else "person_properties" ) materalised_table_column = ( - "properties" if self._person_on_events_mode == PersonsOnEventsMode.disabled else "person_properties" + "properties" if self._person_on_events_mode == PersonsOnEventsMode.DISABLED else "person_properties" ) breakdown_values_expression, breakdown_values_params = get_single_or_multi_property_string_expr( @@ -134,10 +134,12 @@ def get_query(self) -> tuple[str, dict[str, Any]]: self.params.update(prop_params) entity_query, entity_params = self._get_entity_query( - entity=self._filter.target_entity - if self._event_query_type == RetentionQueryType.TARGET - or self._event_query_type == RetentionQueryType.TARGET_FIRST_TIME - else self._filter.returning_entity + entity=( + self._filter.target_entity + if self._event_query_type == RetentionQueryType.TARGET + or self._event_query_type == RetentionQueryType.TARGET_FIRST_TIME + else self._filter.returning_entity + ) ) self.params.update(entity_params) @@ -149,7 +151,7 @@ def get_query(self) -> tuple[str, dict[str, Any]]: null_person_filter = ( f"AND notEmpty({self.EVENT_TABLE_ALIAS}.person_id)" - if self._person_on_events_mode != PersonsOnEventsMode.disabled + if self._person_on_events_mode != PersonsOnEventsMode.DISABLED else "" ) @@ -197,7 +199,7 @@ def _determine_should_join_distinct_ids(self) -> None: self._filter.aggregation_group_type_index is not None or self._aggregate_users_by_distinct_id ) is_using_cohort_propertes = self._column_optimizer.is_using_cohort_propertes - if self._person_on_events_mode == PersonsOnEventsMode.person_id_no_override_properties_on_events or ( + if self._person_on_events_mode == PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS or ( non_person_id_aggregation and not is_using_cohort_propertes ): self._should_join_distinct_ids = False @@ -206,7 +208,7 @@ def _determine_should_join_distinct_ids(self) -> None: def _determine_should_join_persons(self) -> None: EventQuery._determine_should_join_persons(self) - if self._person_on_events_mode != PersonsOnEventsMode.disabled: + if self._person_on_events_mode != PersonsOnEventsMode.DISABLED: self._should_join_persons = False def _get_entity_query(self, entity: Entity): diff --git a/posthog/queries/stickiness/stickiness_event_query.py b/posthog/queries/stickiness/stickiness_event_query.py index 7c8c92222ef95..8810bed83c6ad 100644 --- a/posthog/queries/stickiness/stickiness_event_query.py +++ b/posthog/queries/stickiness/stickiness_event_query.py @@ -43,7 +43,7 @@ def get_query(self) -> tuple[str, dict[str, Any]]: null_person_filter = ( f"AND notEmpty({self.EVENT_TABLE_ALIAS}.person_id)" - if self._person_on_events_mode != PersonsOnEventsMode.disabled + if self._person_on_events_mode != PersonsOnEventsMode.DISABLED else "" ) @@ -82,14 +82,14 @@ def _person_query(self): ) def _determine_should_join_distinct_ids(self) -> None: - if self._person_on_events_mode == PersonsOnEventsMode.person_id_no_override_properties_on_events: + if self._person_on_events_mode == PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS: self._should_join_distinct_ids = False else: self._should_join_distinct_ids = True def _determine_should_join_persons(self) -> None: EventQuery._determine_should_join_persons(self) - if self._person_on_events_mode != PersonsOnEventsMode.disabled: + if self._person_on_events_mode != PersonsOnEventsMode.DISABLED: self._should_join_persons = False def aggregation_target(self): diff --git a/posthog/queries/trends/breakdown.py b/posthog/queries/trends/breakdown.py index fb4c7f2cca2b3..db6fd0860c38f 100644 --- a/posthog/queries/trends/breakdown.py +++ b/posthog/queries/trends/breakdown.py @@ -98,7 +98,7 @@ def __init__( filter: Filter, team: Team, column_optimizer: Optional[ColumnOptimizer] = None, - person_on_events_mode: PersonsOnEventsMode = PersonsOnEventsMode.disabled, + person_on_events_mode: PersonsOnEventsMode = PersonsOnEventsMode.DISABLED, add_person_urls: bool = False, ): self.entity = entity @@ -109,9 +109,9 @@ def __init__( self.column_optimizer = column_optimizer or ColumnOptimizer(self.filter, self.team_id) self.add_person_urls = add_person_urls self.person_on_events_mode = person_on_events_mode - if person_on_events_mode == PersonsOnEventsMode.person_id_override_properties_on_events: + if person_on_events_mode == PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS: self._person_id_alias = f"if(notEmpty({self.PERSON_ID_OVERRIDES_TABLE_ALIAS}.distinct_id), {self.PERSON_ID_OVERRIDES_TABLE_ALIAS}.person_id, {self.EVENT_TABLE_ALIAS}.person_id)" - elif person_on_events_mode == PersonsOnEventsMode.person_id_no_override_properties_on_events: + elif person_on_events_mode == PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS: self._person_id_alias = f"{self.EVENT_TABLE_ALIAS}.person_id" else: self._person_id_alias = f"{self.DISTINCT_ID_TABLE_ALIAS}.person_id" @@ -129,7 +129,7 @@ def _props_to_filter(self) -> tuple[str, dict]: ) target_properties: Optional[PropertyGroup] = props_to_filter - if self.person_on_events_mode == PersonsOnEventsMode.disabled: + if self.person_on_events_mode == PersonsOnEventsMode.DISABLED: target_properties = self.column_optimizer.property_optimizer.parse_property_groups(props_to_filter).outer return parse_prop_grouped_clauses( @@ -160,9 +160,11 @@ def get_query(self) -> tuple[str, dict, Callable]: self.team, filter=self.filter, event_table_alias=self.EVENT_TABLE_ALIAS, - person_id_alias=f"person_id" - if self.person_on_events_mode == PersonsOnEventsMode.person_id_no_override_properties_on_events - else self._person_id_alias, + person_id_alias=( + f"person_id" + if self.person_on_events_mode == PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS + else self._person_id_alias + ), ) action_query = "" @@ -193,13 +195,15 @@ def get_query(self) -> tuple[str, dict, Callable]: "parsed_date_from": parsed_date_from, "parsed_date_to": parsed_date_to, "actions_query": "AND {}".format(action_query) if action_query else "", - "event_filter": "AND event = %(event)s" - if self.entity.type == TREND_FILTER_TYPE_EVENTS and self.entity.id is not None - else "", + "event_filter": ( + "AND event = %(event)s" + if self.entity.type == TREND_FILTER_TYPE_EVENTS and self.entity.id is not None + else "" + ), "filters": prop_filters, - "null_person_filter": f"AND notEmpty(e.person_id)" - if self.person_on_events_mode != PersonsOnEventsMode.disabled - else "", + "null_person_filter": ( + f"AND notEmpty(e.person_id)" if self.person_on_events_mode != PersonsOnEventsMode.DISABLED else "" + ), } _params, _breakdown_filter_params = {}, {} @@ -491,9 +495,11 @@ def _breakdown_prop_params(self, aggregate_operation: str, math_params: dict): return ( { - "values": [*values_arr, breakdown_other_value] - if has_more_values and not self.filter.breakdown_hide_other_aggregation - else values_arr, + "values": ( + [*values_arr, breakdown_other_value] + if has_more_values and not self.filter.breakdown_hide_other_aggregation + else values_arr + ), "breakdown_other_value": breakdown_other_value, "breakdown_null_value": breakdown_null_value, }, @@ -520,7 +526,7 @@ def _get_breakdown_value(self, breakdown: str) -> str: raise ValidationError(f'Invalid breakdown "{breakdown}" for breakdown type "session"') elif ( - self.person_on_events_mode != PersonsOnEventsMode.disabled + self.person_on_events_mode != PersonsOnEventsMode.DISABLED and self.filter.breakdown_type == "group" and groups_on_events_querying_enabled() ): @@ -532,7 +538,7 @@ def _get_breakdown_value(self, breakdown: str) -> str: properties_field, materialised_table_column=properties_field, ) - elif self.person_on_events_mode != PersonsOnEventsMode.disabled and self.filter.breakdown_type != "group": + elif self.person_on_events_mode != PersonsOnEventsMode.DISABLED and self.filter.breakdown_type != "group": if self.filter.breakdown_type == "person": breakdown_value, _ = get_property_string_expr( "events", @@ -626,11 +632,11 @@ def _parse(result: list) -> list: } parsed_params: dict[str, str] = encode_get_request_params({**filter_params, **extra_params}) parsed_result = { - "aggregated_value": float( - correct_result_for_sampling(aggregated_value, filter.sampling_factor, entity.math) - ) - if aggregated_value is not None - else None, + "aggregated_value": ( + float(correct_result_for_sampling(aggregated_value, filter.sampling_factor, entity.math)) + if aggregated_value is not None + else None + ), "filter": filter_params, "persons": { "filter": extra_params, @@ -746,10 +752,10 @@ def _determine_breakdown_label( return str(value) or BREAKDOWN_NULL_DISPLAY def _person_join_condition(self) -> tuple[str, dict]: - if self.person_on_events_mode == PersonsOnEventsMode.person_id_no_override_properties_on_events: + if self.person_on_events_mode == PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS: return "", {} - if self.person_on_events_mode == PersonsOnEventsMode.person_id_override_properties_on_events: + if self.person_on_events_mode == PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS: return ( PERSON_DISTINCT_ID_OVERRIDES_JOIN_SQL.format( person_overrides_table_alias=self.PERSON_ID_OVERRIDES_TABLE_ALIAS, diff --git a/posthog/queries/trends/lifecycle.py b/posthog/queries/trends/lifecycle.py index 199e3c57973b6..141df25134d1d 100644 --- a/posthog/queries/trends/lifecycle.py +++ b/posthog/queries/trends/lifecycle.py @@ -126,12 +126,12 @@ def get_query(self): self.params.update(entity_prop_params) created_at_clause = ( - "person.created_at" if self._person_on_events_mode == PersonsOnEventsMode.disabled else "person_created_at" + "person.created_at" if self._person_on_events_mode == PersonsOnEventsMode.DISABLED else "person_created_at" ) null_person_filter = ( "" - if self._person_on_events_mode == PersonsOnEventsMode.disabled + if self._person_on_events_mode == PersonsOnEventsMode.DISABLED else f"AND notEmpty({self.EVENT_TABLE_ALIAS}.person_id)" ) @@ -187,8 +187,8 @@ def _get_date_filter(self): def _determine_should_join_distinct_ids(self) -> None: self._should_join_distinct_ids = ( - self._person_on_events_mode != PersonsOnEventsMode.person_id_no_override_properties_on_events + self._person_on_events_mode != PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS ) def _determine_should_join_persons(self) -> None: - self._should_join_persons = self._person_on_events_mode == PersonsOnEventsMode.disabled + self._should_join_persons = self._person_on_events_mode == PersonsOnEventsMode.DISABLED diff --git a/posthog/queries/trends/total_volume.py b/posthog/queries/trends/total_volume.py index 5e91d9272cf18..355b3c1d4e721 100644 --- a/posthog/queries/trends/total_volume.py +++ b/posthog/queries/trends/total_volume.py @@ -53,9 +53,9 @@ def _total_volume_query(self, entity: Entity, filter: Filter, team: Team) -> tup interval_func = get_interval_func_ch(filter.interval) person_id_alias = f"{self.DISTINCT_ID_TABLE_ALIAS}.person_id" - if team.person_on_events_mode == PersonsOnEventsMode.person_id_override_properties_on_events: + if team.person_on_events_mode == PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS: person_id_alias = f"if(notEmpty({self.PERSON_ID_OVERRIDES_TABLE_ALIAS}.person_id), {self.PERSON_ID_OVERRIDES_TABLE_ALIAS}.person_id, {self.EVENT_TABLE_ALIAS}.person_id)" - elif team.person_on_events_mode == PersonsOnEventsMode.person_id_no_override_properties_on_events: + elif team.person_on_events_mode == PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS: person_id_alias = f"{self.EVENT_TABLE_ALIAS}.person_id" aggregate_operation, join_condition, math_params = process_math( @@ -70,10 +70,12 @@ def _total_volume_query(self, entity: Entity, filter: Filter, team: Team) -> tup filter=filter, entity=entity, team=team, - should_join_distinct_ids=True - if join_condition != "" - or (entity.math in [WEEKLY_ACTIVE, MONTHLY_ACTIVE] and not team.aggregate_users_by_distinct_id) - else False, + should_join_distinct_ids=( + True + if join_condition != "" + or (entity.math in [WEEKLY_ACTIVE, MONTHLY_ACTIVE] and not team.aggregate_users_by_distinct_id) + else False + ), person_on_events_mode=team.person_on_events_mode, ) event_query_base, event_query_params = trend_event_query.get_query_base() diff --git a/posthog/queries/trends/trends_actors.py b/posthog/queries/trends/trends_actors.py index f7db8b36d8ac3..f69db981d309a 100644 --- a/posthog/queries/trends/trends_actors.py +++ b/posthog/queries/trends/trends_actors.py @@ -61,18 +61,18 @@ def actor_query(self, limit_actors: Optional[bool] = True) -> tuple[str, dict]: value=lower_bound, operator="gte", type=self._filter.breakdown_type, - group_type_index=self._filter.breakdown_group_type_index - if self._filter.breakdown_type == "group" - else None, + group_type_index=( + self._filter.breakdown_group_type_index if self._filter.breakdown_type == "group" else None + ), ), Property( key=self._filter.breakdown, value=upper_bound, operator="lt", type=self._filter.breakdown_type, - group_type_index=self._filter.breakdown_group_type_index - if self._filter.breakdown_type == "group" - else None, + group_type_index=( + self._filter.breakdown_group_type_index if self._filter.breakdown_type == "group" else None + ), ), ] else: @@ -81,9 +81,9 @@ def actor_query(self, limit_actors: Optional[bool] = True) -> tuple[str, dict]: key=self._filter.breakdown, value=self._filter.breakdown_value, type=self._filter.breakdown_type, - group_type_index=self._filter.breakdown_group_type_index - if self._filter.breakdown_type == "group" - else None, + group_type_index=( + self._filter.breakdown_group_type_index if self._filter.breakdown_type == "group" else None + ), ) ] @@ -104,7 +104,7 @@ def actor_query(self, limit_actors: Optional[bool] = True) -> tuple[str, dict]: team=self._team, entity=self.entity, should_join_distinct_ids=not self.is_aggregating_by_groups - and self._team.person_on_events_mode != PersonsOnEventsMode.person_id_no_override_properties_on_events, + and self._team.person_on_events_mode != PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS, extra_event_properties=["$window_id", "$session_id"] if self._filter.include_recordings else [], extra_fields=extra_fields, person_on_events_mode=self._team.person_on_events_mode, diff --git a/posthog/queries/trends/trends_event_query.py b/posthog/queries/trends/trends_event_query.py index b856cb6a035e5..837a8a352a334 100644 --- a/posthog/queries/trends/trends_event_query.py +++ b/posthog/queries/trends/trends_event_query.py @@ -10,7 +10,7 @@ def get_query(self) -> tuple[str, dict[str, Any]]: person_id_field = "" if self._should_join_distinct_ids: person_id_field = f", {self._person_id_alias} as person_id" - elif self._person_on_events_mode == PersonsOnEventsMode.person_id_no_override_properties_on_events: + elif self._person_on_events_mode == PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS: person_id_field = f", {self.EVENT_TABLE_ALIAS}.person_id as person_id" _fields = ( @@ -62,7 +62,7 @@ def get_query(self) -> tuple[str, dict[str, Any]]: return f"SELECT {_fields} {base_query}", params def _get_extra_person_columns(self) -> str: - if self._person_on_events_mode != PersonsOnEventsMode.disabled: + if self._person_on_events_mode != PersonsOnEventsMode.DISABLED: return " ".join( ", {extract} as {column_name}".format( extract=get_property_string_expr( diff --git a/posthog/queries/trends/trends_event_query_base.py b/posthog/queries/trends/trends_event_query_base.py index 8fb17d3579e8f..588307e1a7723 100644 --- a/posthog/queries/trends/trends_event_query_base.py +++ b/posthog/queries/trends/trends_event_query_base.py @@ -80,7 +80,7 @@ def get_query_base(self) -> tuple[str, dict[str, Any]]: return query, self.params def _determine_should_join_distinct_ids(self) -> None: - if self._person_on_events_mode == PersonsOnEventsMode.person_id_no_override_properties_on_events: + if self._person_on_events_mode == PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS: self._should_join_distinct_ids = False is_entity_per_user = self._entity.math in ( @@ -97,7 +97,7 @@ def _determine_should_join_distinct_ids(self) -> None: self._should_join_distinct_ids = True def _determine_should_join_persons(self) -> None: - if self._person_on_events_mode != PersonsOnEventsMode.disabled: + if self._person_on_events_mode != PersonsOnEventsMode.DISABLED: self._should_join_persons = False else: EventQuery._determine_should_join_persons(self) @@ -107,7 +107,7 @@ def _get_not_null_actor_condition(self) -> str: # If aggregating by person, exclude events with null/zero person IDs return ( f"AND notEmpty({self.EVENT_TABLE_ALIAS}.person_id)" - if self._person_on_events_mode != PersonsOnEventsMode.disabled + if self._person_on_events_mode != PersonsOnEventsMode.DISABLED else "" ) else: diff --git a/posthog/queries/trends/util.py b/posthog/queries/trends/util.py index 5f4cef63da072..09f34ff135633 100644 --- a/posthog/queries/trends/util.py +++ b/posthog/queries/trends/util.py @@ -176,9 +176,9 @@ def determine_aggregator(entity: Entity, team: Team) -> str: return f'"$group_{entity.math_group_type_index}"' elif team.aggregate_users_by_distinct_id: return "e.distinct_id" - elif team.person_on_events_mode == PersonsOnEventsMode.person_id_no_override_properties_on_events: + elif team.person_on_events_mode == PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS: return "e.person_id" - elif team.person_on_events_mode == PersonsOnEventsMode.person_id_override_properties_on_events: + elif team.person_on_events_mode == PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS: return f"if(notEmpty(overrides.distinct_id), overrides.person_id, e.person_id)" else: return "pdi.person_id" diff --git a/posthog/queries/util.py b/posthog/queries/util.py index 5cbeea74716b0..44dac7dd8fdb9 100644 --- a/posthog/queries/util.py +++ b/posthog/queries/util.py @@ -178,10 +178,10 @@ def correct_result_for_sampling( def get_person_properties_mode(team: Team) -> PersonPropertiesMode: - if team.person_on_events_mode == PersonsOnEventsMode.disabled: + if team.person_on_events_mode == PersonsOnEventsMode.DISABLED: return PersonPropertiesMode.USING_PERSON_PROPERTIES_COLUMN - if team.person_on_events_mode == PersonsOnEventsMode.person_id_override_properties_on_events: + if team.person_on_events_mode == PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS: return PersonPropertiesMode.DIRECT_ON_EVENTS_WITH_POE_V2 return PersonPropertiesMode.DIRECT_ON_EVENTS diff --git a/posthog/schema.py b/posthog/schema.py index ed138547bf66b..91a96d1c96edd 100644 --- a/posthog/schema.py +++ b/posthog/schema.py @@ -13,50 +13,50 @@ class SchemaRoot(RootModel[Any]): class MathGroupTypeIndex(float, Enum): - number_0 = 0 - number_1 = 1 - number_2 = 2 - number_3 = 3 - number_4 = 4 + NUMBER_0 = 0 + NUMBER_1 = 1 + NUMBER_2 = 2 + NUMBER_3 = 3 + NUMBER_4 = 4 class AggregationAxisFormat(str, Enum): - numeric = "numeric" - duration = "duration" - duration_ms = "duration_ms" - percentage = "percentage" - percentage_scaled = "percentage_scaled" + NUMERIC = "numeric" + DURATION = "duration" + DURATION_MS = "duration_ms" + PERCENTAGE = "percentage" + PERCENTAGE_SCALED = "percentage_scaled" class Kind(str, Enum): - Method = "Method" - Function = "Function" - Constructor = "Constructor" - Field = "Field" - Variable = "Variable" - Class = "Class" - Struct = "Struct" - Interface = "Interface" - Module = "Module" - Property = "Property" - Event = "Event" - Operator = "Operator" - Unit = "Unit" - Value = "Value" - Constant = "Constant" - Enum = "Enum" - EnumMember = "EnumMember" - Keyword = "Keyword" - Text = "Text" - Color = "Color" - File = "File" - Reference = "Reference" - Customcolor = "Customcolor" - Folder = "Folder" - TypeParameter = "TypeParameter" - User = "User" - Issue = "Issue" - Snippet = "Snippet" + METHOD = "Method" + FUNCTION = "Function" + CONSTRUCTOR = "Constructor" + FIELD = "Field" + VARIABLE = "Variable" + CLASS_ = "Class" + STRUCT = "Struct" + INTERFACE = "Interface" + MODULE = "Module" + PROPERTY = "Property" + EVENT = "Event" + OPERATOR = "Operator" + UNIT = "Unit" + VALUE = "Value" + CONSTANT = "Constant" + ENUM = "Enum" + ENUM_MEMBER = "EnumMember" + KEYWORD = "Keyword" + TEXT = "Text" + COLOR = "Color" + FILE = "File" + REFERENCE = "Reference" + CUSTOMCOLOR = "Customcolor" + FOLDER = "Folder" + TYPE_PARAMETER = "TypeParameter" + USER = "User" + ISSUE = "Issue" + SNIPPET = "Snippet" class AutocompleteCompletionItem(BaseModel): @@ -65,7 +65,9 @@ class AutocompleteCompletionItem(BaseModel): ) detail: Optional[str] = Field( default=None, - description="A human-readable string with additional information about this item, like type or symbol information.", + description=( + "A human-readable string with additional information about this item, like type or symbol information." + ), ) documentation: Optional[str] = Field( default=None, description="A human-readable string that represents a doc-comment." @@ -78,34 +80,37 @@ class AutocompleteCompletionItem(BaseModel): ) label: str = Field( ..., - description="The label of this completion item. By default this is also the text that is inserted when selecting this completion.", + description=( + "The label of this completion item. By default this is also the text that is inserted when selecting this" + " completion." + ), ) class BaseMathType(str, Enum): - total = "total" - dau = "dau" - weekly_active = "weekly_active" - monthly_active = "monthly_active" - unique_session = "unique_session" + TOTAL = "total" + DAU = "dau" + WEEKLY_ACTIVE = "weekly_active" + MONTHLY_ACTIVE = "monthly_active" + UNIQUE_SESSION = "unique_session" class BreakdownAttributionType(str, Enum): - first_touch = "first_touch" - last_touch = "last_touch" - all_events = "all_events" - step = "step" + FIRST_TOUCH = "first_touch" + LAST_TOUCH = "last_touch" + ALL_EVENTS = "all_events" + STEP = "step" class BreakdownType(str, Enum): - cohort = "cohort" - person = "person" - event = "event" - group = "group" - session = "session" - hogql = "hogql" - data_warehouse = "data_warehouse" - data_warehouse_person_property = "data_warehouse_person_property" + COHORT = "cohort" + PERSON = "person" + EVENT = "event" + GROUP = "group" + SESSION = "session" + HOGQL = "hogql" + DATA_WAREHOUSE = "data_warehouse" + DATA_WAREHOUSE_PERSON_PROPERTY = "data_warehouse_person_property" class BreakdownValueInt(RootModel[int]): @@ -160,15 +165,15 @@ class ChartAxis(BaseModel): class ChartDisplayType(str, Enum): - ActionsLineGraph = "ActionsLineGraph" - ActionsBar = "ActionsBar" - ActionsAreaGraph = "ActionsAreaGraph" - ActionsLineGraphCumulative = "ActionsLineGraphCumulative" - BoldNumber = "BoldNumber" - ActionsPie = "ActionsPie" - ActionsBarValue = "ActionsBarValue" - ActionsTable = "ActionsTable" - WorldMap = "WorldMap" + ACTIONS_LINE_GRAPH = "ActionsLineGraph" + ACTIONS_BAR = "ActionsBar" + ACTIONS_AREA_GRAPH = "ActionsAreaGraph" + ACTIONS_LINE_GRAPH_CUMULATIVE = "ActionsLineGraphCumulative" + BOLD_NUMBER = "BoldNumber" + ACTIONS_PIE = "ActionsPie" + ACTIONS_BAR_VALUE = "ActionsBarValue" + ACTIONS_TABLE = "ActionsTable" + WORLD_MAP = "WorldMap" class ClickhouseQueryProgress(BaseModel): @@ -193,13 +198,13 @@ class CohortPropertyFilter(BaseModel): class CountPerActorMathType(str, Enum): - avg_count_per_actor = "avg_count_per_actor" - min_count_per_actor = "min_count_per_actor" - max_count_per_actor = "max_count_per_actor" - median_count_per_actor = "median_count_per_actor" - p90_count_per_actor = "p90_count_per_actor" - p95_count_per_actor = "p95_count_per_actor" - p99_count_per_actor = "p99_count_per_actor" + AVG_COUNT_PER_ACTOR = "avg_count_per_actor" + MIN_COUNT_PER_ACTOR = "min_count_per_actor" + MAX_COUNT_PER_ACTOR = "max_count_per_actor" + MEDIAN_COUNT_PER_ACTOR = "median_count_per_actor" + P90_COUNT_PER_ACTOR = "p90_count_per_actor" + P95_COUNT_PER_ACTOR = "p95_count_per_actor" + P99_COUNT_PER_ACTOR = "p99_count_per_actor" class Response3(BaseModel): @@ -243,25 +248,25 @@ class DatabaseSchemaSource(BaseModel): class Type(str, Enum): - posthog = "posthog" - data_warehouse = "data_warehouse" - view = "view" + POSTHOG = "posthog" + DATA_WAREHOUSE = "data_warehouse" + VIEW = "view" class DatabaseSerializedFieldType(str, Enum): - integer = "integer" - float = "float" - string = "string" - datetime = "datetime" - date = "date" - boolean = "boolean" - array = "array" - json = "json" - lazy_table = "lazy_table" - virtual_table = "virtual_table" - field_traverser = "field_traverser" - expression = "expression" - view = "view" + INTEGER = "integer" + FLOAT = "float" + STRING = "string" + DATETIME = "datetime" + DATE = "date" + BOOLEAN = "boolean" + ARRAY = "array" + JSON = "json" + LAZY_TABLE = "lazy_table" + VIRTUAL_TABLE = "virtual_table" + FIELD_TRAVERSER = "field_traverser" + EXPRESSION = "expression" + VIEW = "view" class DateRange(BaseModel): @@ -272,7 +277,10 @@ class DateRange(BaseModel): date_to: Optional[str] = None explicitDate: Optional[bool] = Field( default=False, - description="Whether the date_from and date_to should be used verbatim. Disables rounding to the start and end of period.", + description=( + "Whether the date_from and date_to should be used verbatim. Disables rounding to the start and end of" + " period." + ), ) @@ -284,11 +292,17 @@ class Day(RootModel[int]): root: int +class DurationType(str, Enum): + DURATION = "duration" + ACTIVE_SECONDS = "active_seconds" + INACTIVE_SECONDS = "inactive_seconds" + + class Key(str, Enum): - tag_name = "tag_name" - text = "text" - href = "href" - selector = "selector" + TAG_NAME = "tag_name" + TEXT = "text" + HREF = "href" + SELECTOR = "selector" class ElementType(BaseModel): @@ -314,10 +328,10 @@ class EmptyPropertyFilter(BaseModel): class EntityType(str, Enum): - actions = "actions" - events = "events" - data_warehouse = "data_warehouse" - new_entity = "new_entity" + ACTIONS = "actions" + EVENTS = "events" + DATA_WAREHOUSE = "data_warehouse" + NEW_ENTITY = "new_entity" class EventDefinition(BaseModel): @@ -330,8 +344,8 @@ class EventDefinition(BaseModel): class CorrelationType(str, Enum): - success = "success" - failure = "failure" + SUCCESS = "success" + FAILURE = "failure" class EventOddsRatioSerialized(BaseModel): @@ -388,17 +402,17 @@ class EventsQueryPersonColumn(BaseModel): class FilterLogicalOperator(str, Enum): - AND = "AND" - OR = "OR" + AND_ = "AND" + OR_ = "OR" class FunnelConversionWindowTimeUnit(str, Enum): - second = "second" - minute = "minute" - hour = "hour" - day = "day" - week = "week" - month = "month" + SECOND = "second" + MINUTE = "minute" + HOUR = "hour" + DAY = "day" + WEEK = "week" + MONTH = "month" class FunnelCorrelationResult(BaseModel): @@ -410,9 +424,9 @@ class FunnelCorrelationResult(BaseModel): class FunnelCorrelationResultsType(str, Enum): - events = "events" - properties = "properties" - event_with_properties = "event_with_properties" + EVENTS = "events" + PROPERTIES = "properties" + EVENT_WITH_PROPERTIES = "event_with_properties" class FunnelExclusionLegacy(BaseModel): @@ -438,19 +452,19 @@ class FunnelExclusionSteps(BaseModel): class FunnelLayout(str, Enum): - horizontal = "horizontal" - vertical = "vertical" + HORIZONTAL = "horizontal" + VERTICAL = "vertical" class FunnelPathType(str, Enum): - funnel_path_before_step = "funnel_path_before_step" - funnel_path_between_steps = "funnel_path_between_steps" - funnel_path_after_step = "funnel_path_after_step" + FUNNEL_PATH_BEFORE_STEP = "funnel_path_before_step" + FUNNEL_PATH_BETWEEN_STEPS = "funnel_path_between_steps" + FUNNEL_PATH_AFTER_STEP = "funnel_path_after_step" class FunnelStepReference(str, Enum): - total = "total" - previous = "previous" + TOTAL = "total" + PREVIOUS = "previous" class FunnelTimeToConvertResults(BaseModel): @@ -462,9 +476,9 @@ class FunnelTimeToConvertResults(BaseModel): class FunnelVizType(str, Enum): - steps = "steps" - time_to_convert = "time_to_convert" - trends = "trends" + STEPS = "steps" + TIME_TO_CONVERT = "time_to_convert" + TRENDS = "trends" class GoalLine(BaseModel): @@ -486,40 +500,40 @@ class HogQLNotice(BaseModel): class BounceRatePageViewMode(str, Enum): - count_pageviews = "count_pageviews" - uniq_urls = "uniq_urls" + COUNT_PAGEVIEWS = "count_pageviews" + UNIQ_URLS = "uniq_urls" class InCohortVia(str, Enum): - auto = "auto" - leftjoin = "leftjoin" - subquery = "subquery" - leftjoin_conjoined = "leftjoin_conjoined" + AUTO = "auto" + LEFTJOIN = "leftjoin" + SUBQUERY = "subquery" + LEFTJOIN_CONJOINED = "leftjoin_conjoined" class MaterializationMode(str, Enum): - auto = "auto" - legacy_null_as_string = "legacy_null_as_string" - legacy_null_as_null = "legacy_null_as_null" - disabled = "disabled" + AUTO = "auto" + LEGACY_NULL_AS_STRING = "legacy_null_as_string" + LEGACY_NULL_AS_NULL = "legacy_null_as_null" + DISABLED = "disabled" class PersonsArgMaxVersion(str, Enum): - auto = "auto" - v1 = "v1" - v2 = "v2" + AUTO = "auto" + V1 = "v1" + V2 = "v2" class PersonsJoinMode(str, Enum): - inner = "inner" - left = "left" + INNER = "inner" + LEFT = "left" class PersonsOnEventsMode(str, Enum): - disabled = "disabled" - person_id_no_override_properties_on_events = "person_id_no_override_properties_on_events" - person_id_override_properties_on_events = "person_id_override_properties_on_events" - person_id_override_properties_joined = "person_id_override_properties_joined" + DISABLED = "disabled" + PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS = "person_id_no_override_properties_on_events" + PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS = "person_id_override_properties_on_events" + PERSON_ID_OVERRIDE_PROPERTIES_JOINED = "person_id_override_properties_joined" class HogQLQueryModifiers(BaseModel): @@ -548,8 +562,8 @@ class HogQueryResponse(BaseModel): class Compare(str, Enum): - current = "current" - previous = "previous" + CURRENT = "current" + PREVIOUS = "previous" class DayItem(BaseModel): @@ -580,26 +594,29 @@ class InsightDateRange(BaseModel): date_to: Optional[str] = None explicitDate: Optional[bool] = Field( default=False, - description="Whether the date_from and date_to should be used verbatim. Disables rounding to the start and end of period.", + description=( + "Whether the date_from and date_to should be used verbatim. Disables rounding to the start and end of" + " period." + ), ) class InsightFilterProperty(str, Enum): - trendsFilter = "trendsFilter" - funnelsFilter = "funnelsFilter" - retentionFilter = "retentionFilter" - pathsFilter = "pathsFilter" - stickinessFilter = "stickinessFilter" - lifecycleFilter = "lifecycleFilter" + TRENDS_FILTER = "trendsFilter" + FUNNELS_FILTER = "funnelsFilter" + RETENTION_FILTER = "retentionFilter" + PATHS_FILTER = "pathsFilter" + STICKINESS_FILTER = "stickinessFilter" + LIFECYCLE_FILTER = "lifecycleFilter" class InsightNodeKind(str, Enum): - TrendsQuery = "TrendsQuery" - FunnelsQuery = "FunnelsQuery" - RetentionQuery = "RetentionQuery" - PathsQuery = "PathsQuery" - StickinessQuery = "StickinessQuery" - LifecycleQuery = "LifecycleQuery" + TRENDS_QUERY = "TrendsQuery" + FUNNELS_QUERY = "FunnelsQuery" + RETENTION_QUERY = "RetentionQuery" + PATHS_QUERY = "PathsQuery" + STICKINESS_QUERY = "StickinessQuery" + LIFECYCLE_QUERY = "LifecycleQuery" class InsightType(str, Enum): @@ -615,55 +632,55 @@ class InsightType(str, Enum): class IntervalType(str, Enum): - minute = "minute" - hour = "hour" - day = "day" - week = "week" - month = "month" + MINUTE = "minute" + HOUR = "hour" + DAY = "day" + WEEK = "week" + MONTH = "month" class LifecycleToggle(str, Enum): - new = "new" - resurrecting = "resurrecting" - returning = "returning" - dormant = "dormant" + NEW = "new" + RESURRECTING = "resurrecting" + RETURNING = "returning" + DORMANT = "dormant" class NodeKind(str, Enum): - EventsNode = "EventsNode" - ActionsNode = "ActionsNode" - DataWarehouseNode = "DataWarehouseNode" - EventsQuery = "EventsQuery" - PersonsNode = "PersonsNode" - HogQuery = "HogQuery" - HogQLQuery = "HogQLQuery" - HogQLMetadata = "HogQLMetadata" - HogQLAutocomplete = "HogQLAutocomplete" - ActorsQuery = "ActorsQuery" - FunnelsActorsQuery = "FunnelsActorsQuery" - FunnelCorrelationActorsQuery = "FunnelCorrelationActorsQuery" - SessionsTimelineQuery = "SessionsTimelineQuery" - DataTableNode = "DataTableNode" - DataVisualizationNode = "DataVisualizationNode" - SavedInsightNode = "SavedInsightNode" - InsightVizNode = "InsightVizNode" - TrendsQuery = "TrendsQuery" - FunnelsQuery = "FunnelsQuery" - RetentionQuery = "RetentionQuery" - PathsQuery = "PathsQuery" - StickinessQuery = "StickinessQuery" - LifecycleQuery = "LifecycleQuery" - InsightActorsQuery = "InsightActorsQuery" - InsightActorsQueryOptions = "InsightActorsQueryOptions" - FunnelCorrelationQuery = "FunnelCorrelationQuery" - WebOverviewQuery = "WebOverviewQuery" - WebTopClicksQuery = "WebTopClicksQuery" - WebStatsTableQuery = "WebStatsTableQuery" - TimeToSeeDataSessionsQuery = "TimeToSeeDataSessionsQuery" - TimeToSeeDataQuery = "TimeToSeeDataQuery" - TimeToSeeDataSessionsJSONNode = "TimeToSeeDataSessionsJSONNode" - TimeToSeeDataSessionsWaterfallNode = "TimeToSeeDataSessionsWaterfallNode" - DatabaseSchemaQuery = "DatabaseSchemaQuery" + EVENTS_NODE = "EventsNode" + ACTIONS_NODE = "ActionsNode" + DATA_WAREHOUSE_NODE = "DataWarehouseNode" + EVENTS_QUERY = "EventsQuery" + PERSONS_NODE = "PersonsNode" + HOG_QUERY = "HogQuery" + HOG_QL_QUERY = "HogQLQuery" + HOG_QL_METADATA = "HogQLMetadata" + HOG_QL_AUTOCOMPLETE = "HogQLAutocomplete" + ACTORS_QUERY = "ActorsQuery" + FUNNELS_ACTORS_QUERY = "FunnelsActorsQuery" + FUNNEL_CORRELATION_ACTORS_QUERY = "FunnelCorrelationActorsQuery" + SESSIONS_TIMELINE_QUERY = "SessionsTimelineQuery" + DATA_TABLE_NODE = "DataTableNode" + DATA_VISUALIZATION_NODE = "DataVisualizationNode" + SAVED_INSIGHT_NODE = "SavedInsightNode" + INSIGHT_VIZ_NODE = "InsightVizNode" + TRENDS_QUERY = "TrendsQuery" + FUNNELS_QUERY = "FunnelsQuery" + RETENTION_QUERY = "RetentionQuery" + PATHS_QUERY = "PathsQuery" + STICKINESS_QUERY = "StickinessQuery" + LIFECYCLE_QUERY = "LifecycleQuery" + INSIGHT_ACTORS_QUERY = "InsightActorsQuery" + INSIGHT_ACTORS_QUERY_OPTIONS = "InsightActorsQueryOptions" + FUNNEL_CORRELATION_QUERY = "FunnelCorrelationQuery" + WEB_OVERVIEW_QUERY = "WebOverviewQuery" + WEB_TOP_CLICKS_QUERY = "WebTopClicksQuery" + WEB_STATS_TABLE_QUERY = "WebStatsTableQuery" + TIME_TO_SEE_DATA_SESSIONS_QUERY = "TimeToSeeDataSessionsQuery" + TIME_TO_SEE_DATA_QUERY = "TimeToSeeDataQuery" + TIME_TO_SEE_DATA_SESSIONS_JSON_NODE = "TimeToSeeDataSessionsJSONNode" + TIME_TO_SEE_DATA_SESSIONS_WATERFALL_NODE = "TimeToSeeDataSessionsWaterfallNode" + DATABASE_SCHEMA_QUERY = "DatabaseSchemaQuery" class PathCleaningFilter(BaseModel): @@ -675,10 +692,10 @@ class PathCleaningFilter(BaseModel): class PathType(str, Enum): - field_pageview = "$pageview" - field_screen = "$screen" - custom_event = "custom_event" - hogql = "hogql" + FIELD_PAGEVIEW = "$pageview" + FIELD_SCREEN = "$screen" + CUSTOM_EVENT = "custom_event" + HOGQL = "hogql" class PathsFilter(BaseModel): @@ -724,51 +741,51 @@ class PathsFilterLegacy(BaseModel): class PropertyFilterType(str, Enum): - meta = "meta" - event = "event" - person = "person" - element = "element" - feature = "feature" - session = "session" - cohort = "cohort" - recording = "recording" - group = "group" - hogql = "hogql" - data_warehouse = "data_warehouse" - data_warehouse_person_property = "data_warehouse_person_property" + META = "meta" + EVENT = "event" + PERSON = "person" + ELEMENT = "element" + FEATURE = "feature" + SESSION = "session" + COHORT = "cohort" + RECORDING = "recording" + GROUP = "group" + HOGQL = "hogql" + DATA_WAREHOUSE = "data_warehouse" + DATA_WAREHOUSE_PERSON_PROPERTY = "data_warehouse_person_property" class PropertyMathType(str, Enum): - avg = "avg" - sum = "sum" - min = "min" - max = "max" - median = "median" - p90 = "p90" - p95 = "p95" - p99 = "p99" + AVG = "avg" + SUM = "sum" + MIN = "min" + MAX = "max" + MEDIAN = "median" + P90 = "p90" + P95 = "p95" + P99 = "p99" class PropertyOperator(str, Enum): - exact = "exact" - is_not = "is_not" - icontains = "icontains" - not_icontains = "not_icontains" - regex = "regex" - not_regex = "not_regex" - gt = "gt" - gte = "gte" - lt = "lt" - lte = "lte" - is_set = "is_set" - is_not_set = "is_not_set" - is_date_exact = "is_date_exact" - is_date_before = "is_date_before" - is_date_after = "is_date_after" - between = "between" - not_between = "not_between" - min = "min" - max = "max" + EXACT = "exact" + IS_NOT = "is_not" + ICONTAINS = "icontains" + NOT_ICONTAINS = "not_icontains" + REGEX = "regex" + NOT_REGEX = "not_regex" + GT = "gt" + GTE = "gte" + LT = "lt" + LTE = "lte" + IS_SET = "is_set" + IS_NOT_SET = "is_not_set" + IS_DATE_EXACT = "is_date_exact" + IS_DATE_BEFORE = "is_date_before" + IS_DATE_AFTER = "is_date_after" + BETWEEN = "between" + NOT_BETWEEN = "not_between" + MIN = "min" + MAX = "max" class QueryResponseAlternative1(BaseModel): @@ -852,20 +869,20 @@ class QueryTiming(BaseModel): t: float = Field(..., description="Time in seconds. Shortened to 't' to save on data.") -class RecordingDurationFilter(BaseModel): +class RecordingPropertyFilter(BaseModel): model_config = ConfigDict( extra="forbid", ) - key: Literal["duration"] = "duration" + key: Union[DurationType, str] label: Optional[str] = None operator: PropertyOperator type: Literal["recording"] = "recording" - value: float + value: Optional[Union[str, float, list[Union[str, float]]]] = None class Kind1(str, Enum): - ActionsNode = "ActionsNode" - EventsNode = "EventsNode" + ACTIONS_NODE = "ActionsNode" + EVENTS_NODE = "EventsNode" class RetentionEntity(BaseModel): @@ -882,20 +899,20 @@ class RetentionEntity(BaseModel): class RetentionReference(str, Enum): - total = "total" - previous = "previous" + TOTAL = "total" + PREVIOUS = "previous" class RetentionPeriod(str, Enum): - Hour = "Hour" - Day = "Day" - Week = "Week" - Month = "Month" + HOUR = "Hour" + DAY = "Day" + WEEK = "Week" + MONTH = "Month" class RetentionType(str, Enum): - retention_recurring = "retention_recurring" - retention_first_time = "retention_first_time" + RETENTION_RECURRING = "retention_recurring" + RETENTION_FIRST_TIME = "retention_first_time" class RetentionValue(BaseModel): @@ -925,9 +942,9 @@ class SessionPropertyFilter(BaseModel): class StepOrderValue(str, Enum): - strict = "strict" - unordered = "unordered" - ordered = "ordered" + STRICT = "strict" + UNORDERED = "unordered" + ORDERED = "ordered" class StickinessFilter(BaseModel): @@ -1059,13 +1076,13 @@ class TrendsFilter(BaseModel): model_config = ConfigDict( extra="forbid", ) - aggregationAxisFormat: Optional[AggregationAxisFormat] = AggregationAxisFormat.numeric + aggregationAxisFormat: Optional[AggregationAxisFormat] = AggregationAxisFormat.NUMERIC aggregationAxisPostfix: Optional[str] = None aggregationAxisPrefix: Optional[str] = None breakdown_histogram_bin_count: Optional[float] = None compare: Optional[bool] = False decimalPlaces: Optional[float] = None - display: Optional[ChartDisplayType] = ChartDisplayType.ActionsLineGraph + display: Optional[ChartDisplayType] = ChartDisplayType.ACTIONS_LINE_GRAPH formula: Optional[str] = None hidden_legend_indexes: Optional[list[float]] = None showLabelsOnSeries: Optional[bool] = None @@ -1139,9 +1156,9 @@ class VizSpecificOptions(BaseModel): class Kind2(str, Enum): - unit = "unit" - duration_s = "duration_s" - percentage = "percentage" + UNIT = "unit" + DURATION_S = "duration_s" + PERCENTAGE = "percentage" class WebOverviewItem(BaseModel): @@ -1186,22 +1203,22 @@ class WebOverviewQueryResponse(BaseModel): class WebStatsBreakdown(str, Enum): - Page = "Page" - InitialPage = "InitialPage" - ExitPage = "ExitPage" - InitialChannelType = "InitialChannelType" - InitialReferringDomain = "InitialReferringDomain" - InitialUTMSource = "InitialUTMSource" - InitialUTMCampaign = "InitialUTMCampaign" - InitialUTMMedium = "InitialUTMMedium" - InitialUTMTerm = "InitialUTMTerm" - InitialUTMContent = "InitialUTMContent" - Browser = "Browser" + PAGE = "Page" + INITIAL_PAGE = "InitialPage" + EXIT_PAGE = "ExitPage" + INITIAL_CHANNEL_TYPE = "InitialChannelType" + INITIAL_REFERRING_DOMAIN = "InitialReferringDomain" + INITIAL_UTM_SOURCE = "InitialUTMSource" + INITIAL_UTM_CAMPAIGN = "InitialUTMCampaign" + INITIAL_UTM_MEDIUM = "InitialUTMMedium" + INITIAL_UTM_TERM = "InitialUTMTerm" + INITIAL_UTM_CONTENT = "InitialUTMContent" + BROWSER = "Browser" OS = "OS" - DeviceType = "DeviceType" - Country = "Country" - Region = "Region" - City = "City" + DEVICE_TYPE = "DeviceType" + COUNTRY = "Country" + REGION = "Region" + CITY = "City" class WebStatsTableQueryResponse(BaseModel): @@ -1292,7 +1309,7 @@ class BreakdownFilter(BaseModel): breakdown_histogram_bin_count: Optional[int] = None breakdown_limit: Optional[int] = None breakdown_normalize_url: Optional[bool] = None - breakdown_type: Optional[BreakdownType] = BreakdownType.event + breakdown_type: Optional[BreakdownType] = BreakdownType.EVENT breakdowns: Optional[list[Breakdown]] = None @@ -1859,7 +1876,7 @@ class EventPropertyFilter(BaseModel): ) key: str label: Optional[str] = None - operator: Optional[PropertyOperator] = PropertyOperator.exact + operator: Optional[PropertyOperator] = PropertyOperator.EXACT type: Literal["event"] = Field(default="event", description="Event properties") value: Optional[Union[str, float, list[Union[str, float]]]] = None @@ -2512,7 +2529,7 @@ class RetentionFilter(BaseModel): model_config = ConfigDict( extra="forbid", ) - period: Optional[RetentionPeriod] = RetentionPeriod.Day + period: Optional[RetentionPeriod] = RetentionPeriod.DAY retentionReference: Optional[RetentionReference] = None retentionType: Optional[RetentionType] = None returningEntity: Optional[RetentionEntity] = None @@ -2790,7 +2807,7 @@ class DashboardFilter(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -2843,7 +2860,7 @@ class DataWarehouseNode(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -2874,7 +2891,7 @@ class DataWarehouseNode(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -2916,7 +2933,7 @@ class EntityNode(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -2945,7 +2962,7 @@ class EntityNode(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -2972,7 +2989,7 @@ class EventsNode(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -3003,7 +3020,7 @@ class EventsNode(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -3033,7 +3050,7 @@ class EventsQuery(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -3062,7 +3079,7 @@ class EventsQuery(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -3090,7 +3107,7 @@ class FunnelExclusionActionsNode(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -3122,7 +3139,7 @@ class FunnelExclusionActionsNode(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -3149,7 +3166,7 @@ class FunnelExclusionEventsNode(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -3182,7 +3199,7 @@ class FunnelExclusionEventsNode(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -3209,7 +3226,7 @@ class HogQLFilters(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -3252,7 +3269,7 @@ class PersonsNode(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -3279,7 +3296,7 @@ class PersonsNode(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -3307,7 +3324,7 @@ class PropertyGroupFilterValue(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -3386,7 +3403,7 @@ class ActionsNode(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -3416,7 +3433,7 @@ class ActionsNode(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -3455,19 +3472,19 @@ class FunnelsFilter(BaseModel): extra="forbid", ) binCount: Optional[int] = None - breakdownAttributionType: Optional[BreakdownAttributionType] = BreakdownAttributionType.first_touch + breakdownAttributionType: Optional[BreakdownAttributionType] = BreakdownAttributionType.FIRST_TOUCH breakdownAttributionValue: Optional[int] = None exclusions: Optional[list[Union[FunnelExclusionEventsNode, FunnelExclusionActionsNode]]] = [] funnelAggregateByHogQL: Optional[str] = None funnelFromStep: Optional[int] = None - funnelOrderType: Optional[StepOrderValue] = StepOrderValue.ordered - funnelStepReference: Optional[FunnelStepReference] = FunnelStepReference.total + funnelOrderType: Optional[StepOrderValue] = StepOrderValue.ORDERED + funnelStepReference: Optional[FunnelStepReference] = FunnelStepReference.TOTAL funnelToStep: Optional[int] = None - funnelVizType: Optional[FunnelVizType] = FunnelVizType.steps + funnelVizType: Optional[FunnelVizType] = FunnelVizType.STEPS funnelWindowInterval: Optional[int] = 14 - funnelWindowIntervalUnit: Optional[FunnelConversionWindowTimeUnit] = FunnelConversionWindowTimeUnit.day + funnelWindowIntervalUnit: Optional[FunnelConversionWindowTimeUnit] = FunnelConversionWindowTimeUnit.DAY hidden_legend_breakdowns: Optional[list[str]] = None - layout: Optional[FunnelLayout] = FunnelLayout.vertical + layout: Optional[FunnelLayout] = FunnelLayout.VERTICAL class HasPropertiesNode(RootModel[Union[EventsNode, EventsQuery, PersonsNode]]): @@ -3525,7 +3542,7 @@ class RetentionQuery(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -3551,7 +3568,7 @@ class StickinessQuery(BaseModel): default=False, description="Exclude internal and test users by applying the respective filters" ) interval: Optional[IntervalType] = Field( - default=IntervalType.day, + default=IntervalType.DAY, description="Granularity of the response. Can be one of `hour`, `day`, `week` or `month`", ) kind: Literal["StickinessQuery"] = "StickinessQuery" @@ -3567,7 +3584,7 @@ class StickinessQuery(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -3600,7 +3617,7 @@ class TrendsQuery(BaseModel): default=False, description="Exclude internal and test users by applying the respective filters" ) interval: Optional[IntervalType] = Field( - default=IntervalType.day, + default=IntervalType.DAY, description="Granularity of the response. Can be one of `hour`, `day`, `week` or `month`", ) kind: Literal["TrendsQuery"] = "TrendsQuery" @@ -3616,7 +3633,7 @@ class TrendsQuery(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -3658,7 +3675,10 @@ class FilterType(BaseModel): events: Optional[list[dict[str, Any]]] = None explicit_date: Optional[Union[bool, str]] = Field( default=None, - description='Whether the `date_from` and `date_to` should be used verbatim. Disables rounding to the start and end of period. Strings are cast to bools, e.g. "true" -> true.', + description=( + "Whether the `date_from` and `date_to` should be used verbatim. Disables rounding to the start and end of" + ' period. Strings are cast to bools, e.g. "true" -> true.' + ), ) filter_test_accounts: Optional[bool] = None from_dashboard: Optional[Union[bool, float]] = None @@ -3674,7 +3694,7 @@ class FilterType(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -3718,7 +3738,7 @@ class FunnelsQuery(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -3759,7 +3779,7 @@ class InsightsQueryBaseFunnelsQueryResponse(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -3797,7 +3817,7 @@ class InsightsQueryBaseLifecycleQueryResponse(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -3835,7 +3855,7 @@ class InsightsQueryBasePathsQueryResponse(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -3873,7 +3893,7 @@ class InsightsQueryBaseRetentionQueryResponse(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -3911,7 +3931,7 @@ class InsightsQueryBaseTrendsQueryResponse(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -3937,7 +3957,7 @@ class LifecycleQuery(BaseModel): default=False, description="Exclude internal and test users by applying the respective filters" ) interval: Optional[IntervalType] = Field( - default=IntervalType.day, + default=IntervalType.DAY, description="Granularity of the response. Can be one of `hour`, `day`, `week` or `month`", ) kind: Literal["LifecycleQuery"] = "LifecycleQuery" @@ -3956,7 +3976,7 @@ class LifecycleQuery(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -4075,15 +4095,23 @@ class FunnelsActorsQuery(BaseModel): ) funnelCustomSteps: Optional[list[int]] = Field( default=None, - description="Custom step numbers to get persons for. This overrides `funnelStep`. Primarily for correlation use.", + description=( + "Custom step numbers to get persons for. This overrides `funnelStep`. Primarily for correlation use." + ), ) funnelStep: Optional[int] = Field( default=None, - description="Index of the step for which we want to get the timestamp for, per person. Positive for converted persons, negative for dropped of persons.", + description=( + "Index of the step for which we want to get the timestamp for, per person. Positive for converted persons," + " negative for dropped of persons." + ), ) funnelStepBreakdown: Optional[Union[str, float, list[Union[str, float]]]] = Field( default=None, - description="The breakdown value for which to get persons for. This is an array for person and event properties, a string for groups and an integer for cohorts.", + description=( + "The breakdown value for which to get persons for. This is an array for person and event properties, a" + " string for groups and an integer for cohorts." + ), ) funnelTrendsDropOff: Optional[bool] = None funnelTrendsEntrancePeriodStart: Optional[str] = Field( @@ -4125,7 +4153,7 @@ class PathsQuery(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -4205,7 +4233,7 @@ class FunnelCorrelationActorsQuery(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -4264,7 +4292,10 @@ class ActorsQuery(BaseModel): list[Union[PersonPropertyFilter, CohortPropertyFilter, HogQLPropertyFilter, EmptyPropertyFilter]] ] = Field( default=None, - description="Currently only person filters supported. No filters for querying groups. See `filter_conditions()` in actor_strategies.py.", + description=( + "Currently only person filters supported. No filters for querying groups. See `filter_conditions()` in" + " actor_strategies.py." + ), ) kind: Literal["ActorsQuery"] = "ActorsQuery" limit: Optional[int] = None @@ -4277,7 +4308,10 @@ class ActorsQuery(BaseModel): list[Union[PersonPropertyFilter, CohortPropertyFilter, HogQLPropertyFilter, EmptyPropertyFilter]] ] = Field( default=None, - description="Currently only person filters supported. No filters for querying groups. See `filter_conditions()` in actor_strategies.py.", + description=( + "Currently only person filters supported. No filters for querying groups. See `filter_conditions()` in" + " actor_strategies.py." + ), ) response: Optional[ActorsQueryResponse] = None search: Optional[str] = None @@ -4394,7 +4428,10 @@ class QueryRequest(BaseModel): async_: Optional[bool] = Field( default=None, alias="async", - description="(Experimental) Whether to run the query asynchronously. Defaults to False. If True, the `id` of the query can be used to check the status and to cancel it.", + description=( + "(Experimental) Whether to run the query asynchronously. Defaults to False. If True, the `id` of the query" + " can be used to check the status and to cancel it." + ), examples=[True], ) client_query_id: Optional[str] = Field( @@ -4432,7 +4469,12 @@ class QueryRequest(BaseModel): DatabaseSchemaQuery, ] = Field( ..., - description='Submit a JSON string representing a query for PostHog data analysis, for example a HogQL query.\n\nExample payload:\n\n```\n\n{"query": {"kind": "HogQLQuery", "query": "select * from events limit 100"}}\n\n```\n\nFor more details on HogQL queries, see the [PostHog HogQL documentation](/docs/hogql#api-access).', + description=( + "Submit a JSON string representing a query for PostHog data analysis, for example a HogQL query.\n\nExample" + ' payload:\n\n```\n\n{"query": {"kind": "HogQLQuery", "query": "select * from events limit' + ' 100"}}\n\n```\n\nFor more details on HogQL queries, see the [PostHog HogQL' + " documentation](/docs/hogql#api-access)." + ), discriminator="kind", ) refresh: Optional[Union[bool, str]] = None diff --git a/posthog/schema_helpers.py b/posthog/schema_helpers.py index c50075403ea1a..d3e6cd427b68d 100644 --- a/posthog/schema_helpers.py +++ b/posthog/schema_helpers.py @@ -76,7 +76,7 @@ def serialize_query(self, next_serializer): # use a canonical value for each display category if "display" in dumped[insightFilterKey]: canonical_display = grouped_chart_display_types(dumped[insightFilterKey]["display"]) - if canonical_display == ChartDisplayType.ActionsLineGraph: + if canonical_display == ChartDisplayType.ACTIONS_LINE_GRAPH: del dumped[insightFilterKey]["display"] # default value, remove else: dumped[insightFilterKey]["display"] = canonical_display @@ -125,15 +125,15 @@ def filter_key_for_query(node: InsightQueryNode) -> str: def grouped_chart_display_types(display: ChartDisplayType) -> ChartDisplayType | None: if display in [ - ChartDisplayType.ActionsLineGraph, - ChartDisplayType.ActionsBar, - ChartDisplayType.ActionsAreaGraph, + ChartDisplayType.ACTIONS_LINE_GRAPH, + ChartDisplayType.ACTIONS_BAR, + ChartDisplayType.ACTIONS_AREA_GRAPH, ]: # time series - return ChartDisplayType.ActionsLineGraph - elif display in [ChartDisplayType.ActionsLineGraphCumulative]: + return ChartDisplayType.ACTIONS_LINE_GRAPH + elif display in [ChartDisplayType.ACTIONS_LINE_GRAPH_CUMULATIVE]: # cumulative time series - return ChartDisplayType.ActionsLineGraphCumulative + return ChartDisplayType.ACTIONS_LINE_GRAPH_CUMULATIVE else: # total value - return ChartDisplayType.ActionsBarValue + return ChartDisplayType.ACTIONS_BAR_VALUE diff --git a/posthog/session_recordings/queries/session_recording_list_from_replay_summary.py b/posthog/session_recordings/queries/session_recording_list_from_replay_summary.py index 50de99273228e..78b204d872314 100644 --- a/posthog/session_recordings/queries/session_recording_list_from_replay_summary.py +++ b/posthog/session_recordings/queries/session_recording_list_from_replay_summary.py @@ -194,7 +194,7 @@ def _data_to_return(self, results: list[Any]) -> list[dict[str, Any]]: def get_query(self) -> tuple[str, dict[str, Any]]: # we don't support PoE V1 - hopefully that's ok - if self._person_on_events_mode == PersonsOnEventsMode.person_id_override_properties_on_events: + if self._person_on_events_mode == PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS: return "", {} prop_query, prop_params = self._get_prop_groups( @@ -299,7 +299,7 @@ def _determine_should_join_events(self): ) has_poe_filters = ( - self._person_on_events_mode == PersonsOnEventsMode.person_id_override_properties_on_events + self._person_on_events_mode == PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS and len( [ pg @@ -311,7 +311,7 @@ def _determine_should_join_events(self): ) has_poe_person_filter = ( - self._person_on_events_mode == PersonsOnEventsMode.person_id_override_properties_on_events + self._person_on_events_mode == PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS and self._filter.person_uuid ) @@ -367,9 +367,11 @@ def format_event_filter(self, entity: Entity, prepend: str, team_id: int) -> tup prepend=prepend, allow_denormalized_props=True, has_person_id_joined=True, - person_properties_mode=PersonPropertiesMode.DIRECT_ON_EVENTS_WITH_POE_V2 - if self._person_on_events_mode == PersonsOnEventsMode.person_id_override_properties_on_events - else PersonPropertiesMode.USING_PERSON_PROPERTIES_COLUMN, + person_properties_mode=( + PersonPropertiesMode.DIRECT_ON_EVENTS_WITH_POE_V2 + if self._person_on_events_mode == PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS + else PersonPropertiesMode.USING_PERSON_PROPERTIES_COLUMN + ), hogql_context=self._filter.hogql_context, ) filter_sql += f" {filters}" @@ -416,7 +418,7 @@ def build_event_filters(self) -> SummaryEventFiltersSQL: -- select the unique events in this session to support filtering sessions by presence of an event groupUniqArray(event) as event_names,""" - if self._person_on_events_mode == PersonsOnEventsMode.person_id_override_properties_on_events: + if self._person_on_events_mode == PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS: person_id_clause, person_id_params = self._get_person_id_clause condition_sql += person_id_clause params = {**params, **person_id_params} @@ -493,7 +495,7 @@ def get_query(self, select_event_ids: bool = False) -> tuple[str, dict[str, Any] g for g in self._filter.property_groups.flat if ( - self._person_on_events_mode == PersonsOnEventsMode.person_id_override_properties_on_events + self._person_on_events_mode == PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS and g.type == "person" ) or ( @@ -508,9 +510,11 @@ def get_query(self, select_event_ids: bool = False) -> tuple[str, dict[str, Any] # it is likely this can be returned to the default of True in future # but would need careful monitoring allow_denormalized_props=settings.ALLOW_DENORMALIZED_PROPS_IN_LISTING, - person_properties_mode=PersonPropertiesMode.DIRECT_ON_EVENTS_WITH_POE_V2 - if self._person_on_events_mode == PersonsOnEventsMode.person_id_override_properties_on_events - else PersonPropertiesMode.USING_PERSON_PROPERTIES_COLUMN, + person_properties_mode=( + PersonPropertiesMode.DIRECT_ON_EVENTS_WITH_POE_V2 + if self._person_on_events_mode == PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS + else PersonPropertiesMode.USING_PERSON_PROPERTIES_COLUMN + ), ) ( diff --git a/posthog/tasks/tasks.py b/posthog/tasks/tasks.py index 3e64c3fe7fc6f..d337c5827630d 100644 --- a/posthog/tasks/tasks.py +++ b/posthog/tasks/tasks.py @@ -10,6 +10,7 @@ from redis import Redis from structlog import get_logger +from posthog.clickhouse.client.limit import limit_concurrency, CeleryConcurrencyLimitExceeded from posthog.cloud_utils import is_cloud from posthog.errors import CHQueryErrorTooManySimultaneousQueries from posthog.hogql.constants import LimitContext @@ -40,11 +41,17 @@ def redis_heartbeat() -> None: autoretry_for=( # Important: Only retry for things that might be okay on the next try CHQueryErrorTooManySimultaneousQueries, + CeleryConcurrencyLimitExceeded, ), retry_backoff=1, - retry_backoff_max=2, + retry_backoff_max=10, max_retries=3, + expires=60 * 10, # Do not run queries that got stuck for more than this ) +@limit_concurrency(90) # Do not go above what CH can handle (max_concurrent_queries) +@limit_concurrency( + 10, key=lambda *args, **kwargs: kwargs.get("team_id") or args[0] +) # Do not run too many queries at once for the same team def process_query_task( team_id: int, user_id: Optional[int], @@ -173,7 +180,6 @@ def pg_row_count() -> None: "log_entries", ] - HEARTBEAT_EVENT_TO_INGESTION_LAG_METRIC = { "heartbeat": "ingestion", "heartbeat_buffer": "ingestion_buffer", diff --git a/posthog/temporal/data_imports/pipelines/stripe/__init__.py b/posthog/temporal/data_imports/pipelines/stripe/__init__.py index e69de29bb2d1d..228e94778e689 100644 --- a/posthog/temporal/data_imports/pipelines/stripe/__init__.py +++ b/posthog/temporal/data_imports/pipelines/stripe/__init__.py @@ -0,0 +1,225 @@ +import dlt +from dlt.sources.helpers.rest_client.paginators import BasePaginator +from dlt.sources.helpers.requests import Response, Request +from posthog.temporal.data_imports.pipelines.rest_source import RESTAPIConfig, rest_api_resources +from posthog.temporal.data_imports.pipelines.rest_source.typing import EndpointResource + + +def get_resource(name: str, is_incremental: bool) -> EndpointResource: + resources: dict[str, EndpointResource] = { + "BalanceTransaction": { + "name": "BalanceTransaction", + "table_name": "balance_transaction", + "primary_key": "id", + "write_disposition": "merge", + "endpoint": { + "data_selector": "data", + "path": "/v1/balance_transactions", + "params": { + # the parameters below can optionally be configured + # "created": "OPTIONAL_CONFIG", + # "currency": "OPTIONAL_CONFIG", + # "ending_before": "OPTIONAL_CONFIG", + # "expand": "OPTIONAL_CONFIG", + "limit": 100, + # "payout": "OPTIONAL_CONFIG", + # "source": "OPTIONAL_CONFIG", + # "starting_after": "OPTIONAL_CONFIG", + # "type": "OPTIONAL_CONFIG", + }, + }, + }, + "Charge": { + "name": "Charge", + "table_name": "charge", + "primary_key": "id", + "write_disposition": "merge", + "endpoint": { + "data_selector": "data", + "path": "/v1/charges", + "params": { + # the parameters below can optionally be configured + # "created": "OPTIONAL_CONFIG", + # "customer": "OPTIONAL_CONFIG", + # "ending_before": "OPTIONAL_CONFIG", + # "expand": "OPTIONAL_CONFIG", + "limit": 100, + # "payment_intent": "OPTIONAL_CONFIG", + # "starting_after": "OPTIONAL_CONFIG", + # "transfer_group": "OPTIONAL_CONFIG", + }, + }, + }, + "Customer": { + "name": "Customer", + "table_name": "customer", + "primary_key": "id", + "write_disposition": "merge", + "endpoint": { + "data_selector": "data", + "path": "/v1/customers", + "params": { + # the parameters below can optionally be configured + # "created": "OPTIONAL_CONFIG", + # "email": "OPTIONAL_CONFIG", + # "ending_before": "OPTIONAL_CONFIG", + # "expand": "OPTIONAL_CONFIG", + "limit": 100, + # "starting_after": "OPTIONAL_CONFIG", + # "test_clock": "OPTIONAL_CONFIG", + }, + }, + }, + "Invoice": { + "name": "Invoice", + "table_name": "invoice", + "primary_key": "id", + "write_disposition": "merge", + "endpoint": { + "data_selector": "data", + "path": "/v1/invoices", + "params": { + # the parameters below can optionally be configured + # "collection_method": "OPTIONAL_CONFIG", + "created[gte]": { + "type": "incremental", + "cursor_path": "created", + "initial_value": 0, # type: ignore + } + if is_incremental + else None, + # "customer": "OPTIONAL_CONFIG", + # "due_date": "OPTIONAL_CONFIG", + # "ending_before": "OPTIONAL_CONFIG", + # "expand": "OPTIONAL_CONFIG", + "limit": 100, + # "starting_after": "OPTIONAL_CONFIG", + # "status": "OPTIONAL_CONFIG", + # "subscription": "OPTIONAL_CONFIG", + }, + }, + }, + "Price": { + "name": "Price", + "table_name": "price", + "primary_key": "id", + "write_disposition": "merge", + "endpoint": { + "data_selector": "data", + "path": "/v1/prices", + "params": { + # the parameters below can optionally be configured + # "active": "OPTIONAL_CONFIG", + # "created": "OPTIONAL_CONFIG", + # "currency": "OPTIONAL_CONFIG", + # "ending_before": "OPTIONAL_CONFIG", + # "expand": "OPTIONAL_CONFIG", + "limit": 100, + # "lookup_keys": "OPTIONAL_CONFIG", + # "product": "OPTIONAL_CONFIG", + # "recurring": "OPTIONAL_CONFIG", + # "starting_after": "OPTIONAL_CONFIG", + # "type": "OPTIONAL_CONFIG", + }, + }, + }, + "Product": { + "name": "Product", + "table_name": "product", + "primary_key": "id", + "write_disposition": "merge", + "endpoint": { + "data_selector": "data", + "path": "/v1/products", + "params": { + # the parameters below can optionally be configured + # "active": "OPTIONAL_CONFIG", + # "created": "OPTIONAL_CONFIG", + # "ending_before": "OPTIONAL_CONFIG", + # "expand": "OPTIONAL_CONFIG", + # "ids": "OPTIONAL_CONFIG", + "limit": 100, + # "shippable": "OPTIONAL_CONFIG", + # "starting_after": "OPTIONAL_CONFIG", + # "url": "OPTIONAL_CONFIG", + }, + }, + }, + "Subscription": { + "name": "Subscription", + "table_name": "subscription", + "primary_key": "id", + "write_disposition": "merge", + "endpoint": { + "data_selector": "data", + "path": "/v1/subscriptions", + "params": { + # the parameters below can optionally be configured + # "collection_method": "OPTIONAL_CONFIG", + # "created": "OPTIONAL_CONFIG", + # "current_period_end": "OPTIONAL_CONFIG", + # "current_period_start": "OPTIONAL_CONFIG", + # "customer": "OPTIONAL_CONFIG", + # "ending_before": "OPTIONAL_CONFIG", + # "expand": "OPTIONAL_CONFIG", + "limit": 100, + # "price": "OPTIONAL_CONFIG", + # "starting_after": "OPTIONAL_CONFIG", + # "status": "OPTIONAL_CONFIG", + # "test_clock": "OPTIONAL_CONFIG", + }, + }, + }, + } + + return resources[name] + + +class StripePaginator(BasePaginator): + def update_state(self, response: Response) -> None: + res = response.json() + + self._starting_after = None + + if not res: + self._has_next_page = False + return + + if res["has_more"]: + self._has_next_page = True + + earliest_value_in_response = res["data"][-1]["id"] + self._starting_after = earliest_value_in_response + else: + self._has_next_page = False + + def update_request(self, request: Request) -> None: + if request.params is None: + request.params = {} + + request.params["starting_after"] = self._starting_after + + +@dlt.source(max_table_nesting=0) +def stripe_source(api_key: str, account_id: str, endpoint: str, is_incremental: bool = False): + config: RESTAPIConfig = { + "client": { + "base_url": "https://api.stripe.com/", + "auth": { + "type": "http_basic", + "username": api_key, + "password": "", + }, + "headers": { + "Stripe-Account": account_id, + }, + "paginator": StripePaginator(), + }, + "resource_defaults": { + "primary_key": "id", + "write_disposition": "merge", + }, + "resources": [get_resource(endpoint, is_incremental)], + } + + yield from rest_api_resources(config) diff --git a/posthog/temporal/data_imports/pipelines/stripe/helpers.py b/posthog/temporal/data_imports/pipelines/stripe/helpers.py deleted file mode 100644 index 2dfad33b4a5a0..0000000000000 --- a/posthog/temporal/data_imports/pipelines/stripe/helpers.py +++ /dev/null @@ -1,178 +0,0 @@ -"""Stripe analytics source helpers""" - -from typing import Any, Optional, Union -from collections.abc import Iterable - -import stripe -import dlt -from dlt.common import pendulum -from dlt.sources import DltResource -from pendulum import DateTime -from asgiref.sync import sync_to_async -from posthog.temporal.common.logger import bind_temporal_worker_logger -from posthog.temporal.data_imports.pipelines.helpers import check_limit -from posthog.temporal.data_imports.pipelines.stripe.settings import INCREMENTAL_ENDPOINTS -from posthog.warehouse.models import ExternalDataJob - -from posthog.warehouse.models.external_table_definitions import get_dlt_mapping_for_external_table - -stripe.api_version = "2022-11-15" - - -def transform_date(date: Union[str, DateTime, int]) -> int: - if isinstance(date, str): - date = pendulum.from_format(date, "%Y-%m-%dT%H:%M:%SZ") - if isinstance(date, DateTime): - # convert to unix timestamp - date = int(date.timestamp()) - return date - - -async def stripe_get_data( - api_key: str, - account_id: str, - resource: str, - start_date: Optional[Any] = None, - end_date: Optional[Any] = None, - **kwargs: Any, -) -> dict[Any, Any]: - if start_date: - start_date = transform_date(start_date) - if end_date: - end_date = transform_date(end_date) - - if resource == "Subscription": - kwargs.update({"status": "all"}) - - _resource = getattr(stripe, resource) - - resource_dict = await sync_to_async(_resource.list)( - api_key=api_key, - stripe_account=account_id, - created={"gte": start_date, "lt": end_date}, - limit=100, - **kwargs, - ) - response = dict(resource_dict) - - return response - - -async def stripe_pagination( - api_key: str, - account_id: str, - endpoint: str, - team_id: int, - job_id: str, - schema_id: str, - starting_after: Optional[Any] = None, - start_date: Optional[Any] = None, - end_date: Optional[Any] = None, -): - """ - Retrieves data from an endpoint with pagination. - - Args: - endpoint (str): The endpoint to retrieve data from. - start_date (Optional[Any]): An optional start date to limit the data retrieved. Defaults to None. - end_date (Optional[Any]): An optional end date to limit the data retrieved. Defaults to None. - - Returns: - Iterable[TDataItem]: Data items retrieved from the endpoint. - """ - - logger = await bind_temporal_worker_logger(team_id) - logger.info(f"Stripe: getting {endpoint}") - - if endpoint in INCREMENTAL_ENDPOINTS: - _cursor_state = dlt.current.resource_state(f"team_{team_id}_{schema_id}_{endpoint}").setdefault( - "cursors", {"ending_before": None, "starting_after": None} - ) - _starting_after = _cursor_state.get("starting_after", None) - _ending_before = _cursor_state.get("ending_before", None) if _starting_after is None else None - else: - _starting_after = starting_after - _ending_before = None - - while True: - if _ending_before is not None: - logger.info(f"Stripe: getting {endpoint} before {_ending_before}") - elif _starting_after is not None: - logger.info(f"Stripe: getting {endpoint} after {_starting_after}") - - count = 0 - - response = await stripe_get_data( - api_key, - account_id, - endpoint, - ending_before=_ending_before, - starting_after=_starting_after, - start_date=start_date, - end_date=end_date, - ) - - if len(response["data"]) > 0: - latest_value_in_response = response["data"][0]["id"] - earliest_value_in_response = response["data"][-1]["id"] - - if endpoint in INCREMENTAL_ENDPOINTS: - # First pass, store the latest value - if _starting_after is None and _ending_before is None: - _cursor_state["ending_before"] = latest_value_in_response - - # currently scrolling from past to present - if _ending_before is not None: - _cursor_state["ending_before"] = latest_value_in_response - _ending_before = latest_value_in_response - # otherwise scrolling from present to past - else: - _starting_after = earliest_value_in_response - _cursor_state["starting_after"] = earliest_value_in_response - else: - _starting_after = earliest_value_in_response - else: - if endpoint in INCREMENTAL_ENDPOINTS: - _cursor_state["starting_after"] = None - - yield response["data"] - - count, status = await check_limit( - team_id=team_id, - job_id=job_id, - new_count=count + len(response["data"]), - ) - - if not response["has_more"] or status == ExternalDataJob.Status.CANCELLED: - break - - -@dlt.source(max_table_nesting=0) -def stripe_source( - api_key: str, - account_id: str, - endpoints: tuple[str, ...], - team_id, - job_id, - schema_id, - starting_after: Optional[str] = None, - start_date: Optional[Any] = None, - end_date: Optional[Any] = None, -) -> Iterable[DltResource]: - for endpoint in endpoints: - yield dlt.resource( - stripe_pagination, - name=endpoint, - write_disposition="append", - columns=get_dlt_mapping_for_external_table(f"stripe_{endpoint}".lower()), - )( - api_key=api_key, - account_id=account_id, - endpoint=endpoint, - team_id=team_id, - job_id=job_id, - schema_id=schema_id, - starting_after=starting_after, - start_date=start_date, - end_date=end_date, - ) diff --git a/posthog/temporal/data_imports/pipelines/test/test_pipeline.py b/posthog/temporal/data_imports/pipelines/test/test_pipeline.py index 071b818b01d1c..23fc37c6d80f8 100644 --- a/posthog/temporal/data_imports/pipelines/test/test_pipeline.py +++ b/posthog/temporal/data_imports/pipelines/test/test_pipeline.py @@ -6,7 +6,7 @@ import structlog from asgiref.sync import sync_to_async from posthog.temporal.data_imports.pipelines.pipeline import DataImportPipeline, PipelineInputs -from posthog.temporal.data_imports.pipelines.stripe.helpers import stripe_source +from posthog.temporal.data_imports.pipelines.stripe import stripe_source from posthog.test.base import APIBaseTest from posthog.warehouse.models.external_data_job import ExternalDataJob from posthog.warehouse.models.external_data_schema import ExternalDataSchema @@ -43,22 +43,13 @@ async def _create_pipeline(self, schema_name: str, incremental: bool): pipeline = DataImportPipeline( inputs=PipelineInputs( source_id=source.pk, - run_id=job.pk, + run_id=str(job.pk), schema_id=schema.pk, dataset_name=job.folder_path, job_type="Stripe", team_id=self.team.pk, ), - source=stripe_source( - api_key="", - account_id="", - endpoints=(schema_name,), - team_id=self.team.pk, - job_id=job.pk, - schema_id=schema.pk, - start_date=None, - end_date=None, - ), + source=stripe_source(api_key="", account_id="", endpoint=schema_name, is_incremental=False), logger=structlog.get_logger(), incremental=incremental, ) diff --git a/posthog/temporal/data_imports/workflow_activities/import_data.py b/posthog/temporal/data_imports/workflow_activities/import_data.py index 27b3866db49c8..9062d389415ed 100644 --- a/posthog/temporal/data_imports/workflow_activities/import_data.py +++ b/posthog/temporal/data_imports/workflow_activities/import_data.py @@ -1,5 +1,4 @@ import dataclasses -import datetime as dt from typing import Any import uuid @@ -11,7 +10,6 @@ from posthog.temporal.data_imports.pipelines.zendesk.credentials import ZendeskCredentialsToken from posthog.temporal.data_imports.pipelines.pipeline import DataImportPipeline, PipelineInputs -from posthog.utils import get_instance_region from posthog.warehouse.models import ( ExternalDataJob, ExternalDataSource, @@ -19,7 +17,6 @@ ) from posthog.temporal.common.logger import bind_temporal_worker_logger import asyncio -from django.utils import timezone from structlog.typing import FilteringBoundLogger from posthog.warehouse.models.external_data_schema import ExternalDataSchema, aget_schema_by_id from posthog.warehouse.models.ssh_tunnel import SSHTunnel @@ -56,7 +53,7 @@ async def import_data_activity(inputs: ImportDataActivityInputs) -> tuple[TSchem source = None if model.pipeline.source_type == ExternalDataSource.Type.STRIPE: - from posthog.temporal.data_imports.pipelines.stripe.helpers import stripe_source + from posthog.temporal.data_imports.pipelines.stripe import stripe_source stripe_secret_key = model.pipeline.job_inputs.get("stripe_secret_key", None) account_id = model.pipeline.job_inputs.get("stripe_account_id", None) @@ -65,24 +62,9 @@ async def import_data_activity(inputs: ImportDataActivityInputs) -> tuple[TSchem if not stripe_secret_key: raise ValueError(f"Stripe secret key not found for job {model.id}") - # Hacky just for specific user - region = get_instance_region() - if region == "EU" and inputs.team_id == 11870: - start_date = timezone.now().replace(day=1, hour=0, minute=0, second=0, microsecond=0) - end_date = start_date + dt.timedelta(weeks=5) - else: - start_date = None - end_date = None - + # TODO: add in check_limit to rest_source source = stripe_source( - api_key=stripe_secret_key, - account_id=account_id, - endpoints=tuple(endpoints), - team_id=inputs.team_id, - job_id=inputs.run_id, - schema_id=str(inputs.schema_id), - start_date=start_date, - end_date=end_date, + api_key=stripe_secret_key, account_id=account_id, endpoint=schema.name, is_incremental=schema.is_incremental ) return await _run(job_inputs=job_inputs, source=source, logger=logger, inputs=inputs, schema=schema) diff --git a/posthog/temporal/tests/external_data/test_external_data_job.py b/posthog/temporal/tests/external_data/test_external_data_job.py index 46ab3f2d903df..33363e24d5854 100644 --- a/posthog/temporal/tests/external_data/test_external_data_job.py +++ b/posthog/temporal/tests/external_data/test_external_data_job.py @@ -1,6 +1,6 @@ import uuid from unittest import mock -from typing import Optional +from typing import Any, Optional import pytest from asgiref.sync import sync_to_async from django.test import override_settings @@ -41,12 +41,11 @@ import aioboto3 import functools from django.conf import settings +from dlt.sources.helpers.rest_client.client import RESTClient import asyncio import psycopg -from posthog.temporal.tests.utils.s3 import read_parquet_from_s3 from posthog.warehouse.models.external_data_schema import get_all_schemas_for_source_id -from posthog.warehouse.models.external_table_definitions import get_imported_fields_for_table BUCKET_NAME = "test-external-data-jobs" SESSION = aioboto3.Session() @@ -125,7 +124,7 @@ async def postgres_connection(postgres_config, setup_postgres_test_db): async def _create_schema(schema_name: str, source: ExternalDataSource, team: Team, table_id: Optional[str] = None): return await sync_to_async(ExternalDataSchema.objects.create)( name=schema_name, - team_id=team.id, + team_id=team.pk, source_id=source.pk, table_id=table_id, ) @@ -271,7 +270,7 @@ async def setup_job_1(): team=team, status="running", source_type="Stripe", - job_inputs={"stripe_secret_key": "test-key"}, + job_inputs={"stripe_secret_key": "test-key", "stripe_account_id": "acct_id"}, ) new_job: ExternalDataJob = await sync_to_async(ExternalDataJob.objects.create)( @@ -287,7 +286,7 @@ async def setup_job_1(): inputs = ImportDataActivityInputs( team_id=team.id, - run_id=new_job.pk, + run_id=str(new_job.pk), source_id=new_source.pk, schema_id=customer_schema.id, ) @@ -302,7 +301,7 @@ async def setup_job_2(): team=team, status="running", source_type="Stripe", - job_inputs={"stripe_secret_key": "test-key"}, + job_inputs={"stripe_secret_key": "test-key", "stripe_account_id": "acct_id"}, ) new_job: ExternalDataJob = await sync_to_async(ExternalDataJob.objects.create)( @@ -318,7 +317,7 @@ async def setup_job_2(): inputs = ImportDataActivityInputs( team_id=team.id, - run_id=new_job.pk, + run_id=str(new_job.pk), source_id=new_source.pk, schema_id=charge_schema.id, ) @@ -328,9 +327,48 @@ async def setup_job_2(): job_1, job_1_inputs = await setup_job_1() job_2, job_2_inputs = await setup_job_2() + def mock_customers_paginate( + class_self, + path: str = "", + method: Any = "GET", + params: Optional[dict[str, Any]] = None, + json: Optional[dict[str, Any]] = None, + auth: Optional[Any] = None, + paginator: Optional[Any] = None, + data_selector: Optional[Any] = None, + hooks: Optional[Any] = None, + ): + return iter( + [ + { + "id": "cus_123", + "name": "John Doe", + } + ] + ) + + def mock_charges_paginate( + class_self, + path: str = "", + method: Any = "GET", + params: Optional[dict[str, Any]] = None, + json: Optional[dict[str, Any]] = None, + auth: Optional[Any] = None, + paginator: Optional[Any] = None, + data_selector: Optional[Any] = None, + hooks: Optional[Any] = None, + ): + return iter( + [ + { + "id": "chg_123", + "customer": "cus_1", + } + ] + ) + with ( - mock.patch("stripe.Customer.list") as mock_customer_list, - mock.patch("stripe.Charge.list") as mock_charge_list, + mock.patch.object(RESTClient, "paginate", mock_customers_paginate), override_settings( BUCKET_URL=f"s3://{BUCKET_NAME}", AIRBYTE_BUCKET_KEY=settings.OBJECT_STORAGE_ACCESS_KEY_ID, @@ -341,28 +379,8 @@ async def setup_job_2(): return_value={"clickhouse": {"id": "string", "name": "string"}}, ), ): - mock_customer_list.return_value = { - "data": [ - { - "id": "cus_123", - "name": "John Doe", - } - ], - "has_more": False, - } - - mock_charge_list.return_value = { - "data": [ - { - "id": "chg_123", - "customer": "cus_1", - } - ], - "has_more": False, - } await asyncio.gather( activity_environment.run(import_data_activity, job_1_inputs), - activity_environment.run(import_data_activity, job_2_inputs), ) job_1_customer_objects = await minio_client.list_objects_v2( @@ -370,37 +388,28 @@ async def setup_job_2(): ) assert len(job_1_customer_objects["Contents"]) == 1 - s3_data = await read_parquet_from_s3( - BUCKET_NAME, - job_1_customer_objects["Contents"][0]["Key"], - {}, - settings.OBJECT_STORAGE_ACCESS_KEY_ID, - settings.OBJECT_STORAGE_SECRET_ACCESS_KEY, - ) - customer_fields = get_imported_fields_for_table("stripe_customer") - all_keys = list(s3_data[0].keys()) - assert len(s3_data) == 1 - assert all(field in all_keys for field in customer_fields) + with ( + mock.patch.object(RESTClient, "paginate", mock_charges_paginate), + override_settings( + BUCKET_URL=f"s3://{BUCKET_NAME}", + AIRBYTE_BUCKET_KEY=settings.OBJECT_STORAGE_ACCESS_KEY_ID, + AIRBYTE_BUCKET_SECRET=settings.OBJECT_STORAGE_SECRET_ACCESS_KEY, + ), + mock.patch( + "posthog.warehouse.models.table.DataWarehouseTable.get_columns", + return_value={"clickhouse": {"id": "string", "name": "string"}}, + ), + ): + await asyncio.gather( + activity_environment.run(import_data_activity, job_2_inputs), + ) job_2_charge_objects = await minio_client.list_objects_v2( Bucket=BUCKET_NAME, Prefix=f"{job_2.folder_path}/charge/" ) assert len(job_2_charge_objects["Contents"]) == 1 - s3_data = await read_parquet_from_s3( - BUCKET_NAME, - job_2_charge_objects["Contents"][0]["Key"], - {}, - settings.OBJECT_STORAGE_ACCESS_KEY_ID, - settings.OBJECT_STORAGE_SECRET_ACCESS_KEY, - ) - customer_fields = get_imported_fields_for_table("stripe_charge") - all_keys = list(s3_data[0].keys()) - - assert len(s3_data) == 1 - assert all(field in all_keys for field in customer_fields) - @pytest.mark.django_db(transaction=True) @pytest.mark.asyncio @@ -413,7 +422,7 @@ async def setup_job_1(): team=team, status="running", source_type="Stripe", - job_inputs={"stripe_secret_key": "test-key"}, + job_inputs={"stripe_secret_key": "test-key", "stripe_account_id": "acct_id"}, ) # Already canceled so it should only run once @@ -431,7 +440,7 @@ async def setup_job_1(): inputs = ImportDataActivityInputs( team_id=team.id, - run_id=new_job.pk, + run_id=str(new_job.pk), source_id=new_source.pk, schema_id=customer_schema.id, ) @@ -440,8 +449,28 @@ async def setup_job_1(): job_1, job_1_inputs = await setup_job_1() + def mock_customers_paginate( + class_self, + path: str = "", + method: Any = "GET", + params: Optional[dict[str, Any]] = None, + json: Optional[dict[str, Any]] = None, + auth: Optional[Any] = None, + paginator: Optional[Any] = None, + data_selector: Optional[Any] = None, + hooks: Optional[Any] = None, + ): + return iter( + [ + { + "id": "cus_123", + "name": "John Doe", + } + ] + ) + with ( - mock.patch("stripe.Customer.list") as mock_customer_list, + mock.patch.object(RESTClient, "paginate", mock_customers_paginate), override_settings( BUCKET_URL=f"s3://{BUCKET_NAME}", AIRBYTE_BUCKET_KEY=settings.OBJECT_STORAGE_ACCESS_KEY_ID, @@ -452,15 +481,6 @@ async def setup_job_1(): return_value={"clickhouse": {"id": "string", "name": "string"}}, ), ): - mock_customer_list.return_value = { - "data": [ - { - "id": "cus_123", - "name": "John Doe", - } - ], - "has_more": True, - } await asyncio.gather( activity_environment.run(import_data_activity, job_1_inputs), ) @@ -487,7 +507,7 @@ async def setup_job_1(): team=team, status="running", source_type="Stripe", - job_inputs={"stripe_secret_key": "test-key"}, + job_inputs={"stripe_secret_key": "test-key", "stripe_account_id": "acct_id"}, ) new_job: ExternalDataJob = await sync_to_async(ExternalDataJob.objects.create)( @@ -503,7 +523,7 @@ async def setup_job_1(): inputs = ImportDataActivityInputs( team_id=team.id, - run_id=new_job.pk, + run_id=str(new_job.pk), source_id=new_source.pk, schema_id=customer_schema.id, ) @@ -512,8 +532,28 @@ async def setup_job_1(): job_1, job_1_inputs = await setup_job_1() + def mock_customers_paginate( + class_self, + path: str = "", + method: Any = "GET", + params: Optional[dict[str, Any]] = None, + json: Optional[dict[str, Any]] = None, + auth: Optional[Any] = None, + paginator: Optional[Any] = None, + data_selector: Optional[Any] = None, + hooks: Optional[Any] = None, + ): + return iter( + [ + { + "id": "cus_123", + "name": "John Doe", + } + ] + ) + with ( - mock.patch("stripe.Customer.list") as mock_customer_list, + mock.patch.object(RESTClient, "paginate", mock_customers_paginate), mock.patch("posthog.temporal.data_imports.pipelines.helpers.CHUNK_SIZE", 0), override_settings( BUCKET_URL=f"s3://{BUCKET_NAME}", @@ -525,15 +565,6 @@ async def setup_job_1(): return_value={"clickhouse": {"id": "string", "name": "string"}}, ), ): - mock_customer_list.return_value = { - "data": [ - { - "id": "cus_123", - "name": "John Doe", - } - ], - "has_more": False, - } await asyncio.gather( activity_environment.run(import_data_activity, job_1_inputs), ) @@ -561,7 +592,7 @@ async def test_external_data_job_workflow_with_schema(team, **kwargs): team=team, status="running", source_type="Stripe", - job_inputs={"stripe_secret_key": "test-key"}, + job_inputs={"stripe_secret_key": "test-key", "stripe_account_id": "acct_id"}, ) schema = await sync_to_async(ExternalDataSchema.objects.create)( @@ -657,7 +688,7 @@ async def setup_job_1(): posthog_test_schema = await _create_schema("posthog_test", new_source, team) inputs = ImportDataActivityInputs( - team_id=team.id, run_id=new_job.pk, source_id=new_source.pk, schema_id=posthog_test_schema.id + team_id=team.id, run_id=str(new_job.pk), source_id=new_source.pk, schema_id=posthog_test_schema.id ) return new_job, inputs @@ -689,7 +720,7 @@ async def test_check_schedule_activity_with_schema_id(activity_environment, team team=team, status="running", source_type="Stripe", - job_inputs={"stripe_secret_key": "test-key"}, + job_inputs={"stripe_secret_key": "test-key", "stripe_account_id": "acct_id"}, ) test_1_schema = await _create_schema("test-1", new_source, team) @@ -716,7 +747,7 @@ async def test_check_schedule_activity_with_missing_schema_id_but_with_schedule( team=team, status="running", source_type="Stripe", - job_inputs={"stripe_secret_key": "test-key"}, + job_inputs={"stripe_secret_key": "test-key", "stripe_account_id": "acct_id"}, ) await sync_to_async(ExternalDataSchema.objects.create)( @@ -760,7 +791,7 @@ async def test_check_schedule_activity_with_missing_schema_id_and_no_schedule(ac team=team, status="running", source_type="Stripe", - job_inputs={"stripe_secret_key": "test-key"}, + job_inputs={"stripe_secret_key": "test-key", "stripe_account_id": "acct_id"}, ) await sync_to_async(ExternalDataSchema.objects.create)( diff --git a/posthog/test/test_migration_0410.py b/posthog/test/test_migration_0410.py deleted file mode 100644 index 07e33eaf756de..0000000000000 --- a/posthog/test/test_migration_0410.py +++ /dev/null @@ -1,67 +0,0 @@ -from posthog.test.base import NonAtomicTestMigrations - -from posthog.models.action import Action -from posthog.models.action.action_step import ActionStep -from posthog.models.team import Team -from posthog.models.organization import Organization - - -class TestActionStepsJSONMigration(NonAtomicTestMigrations): - migrate_from = "0409_action_steps_json_alter_actionstep_action" - migrate_to = "0410_action_steps_population" - - CLASS_DATA_LEVEL_SETUP = False - - def setUpBeforeMigration(self, apps): - org = Organization.objects.create(name="o1") - team = Team.objects.create(name="t1", organization=org) - - # Create a lot of actions - - for i in range(1000): - # We create this with sql as it won't have the new fields - sql = f"""INSERT INTO posthog_action (name, team_id, description, created_at, updated_at, deleted, post_to_slack, slack_message_format, is_calculating, last_calculated_at) - VALUES ('action{i}', {team.pk}, '', '2022-01-01', '2022-01-01', FALSE, FALSE, '', FALSE, '2022-01-01') RETURNING id; - """ - - action = Action.objects.raw(sql)[0] - - # We create this with sql as it won't have the new fields - sql = f"""INSERT INTO posthog_actionstep (action_id, tag_name, text, text_matching, href, href_matching, selector, url, url_matching, event, properties) - VALUES ({action.pk}, 'tag1', 'text1', 'exact', 'href1', 'exact', 'selector1', 'url1', 'exact', 'event1', '{{"key1": "value1"}}') RETURNING id; - """ - - ActionStep.objects.raw(sql)[0] - - def test_migrate_action_steps(self): - apps = self.apps - if apps is None: - # obey mypy - raise Exception("apps is None") - - all_actions = Action.objects.prefetch_related("action_steps").all() - - assert len(all_actions) == 1000 - - for action in all_actions: - assert action.steps_json == [ - { - "tag_name": "tag1", - "text": "text1", - "text_matching": "exact", - "href": "href1", - "href_matching": "exact", - "selector": "selector1", - "url": "url1", - "url_matching": "exact", - "event": "event1", - "properties": {"key1": "value1"}, - } - ], action - - def tearDown(self): - # Clean up all the steps and actions - ActionStep.objects.raw("DELETE FROM posthog_actionstep") - Action.objects.raw("DELETE FROM posthog_action") - - super().tearDown() diff --git a/posthog/test/test_schema_helpers.py b/posthog/test/test_schema_helpers.py index 012bf21d0c3d9..a0ca3ec5d789d 100644 --- a/posthog/test/test_schema_helpers.py +++ b/posthog/test/test_schema_helpers.py @@ -35,8 +35,8 @@ def test_serializes_to_differing_json_for_default_value(self): equal property filters can be distinguished. """ - q1 = EventPropertyFilter(key="abc", operator=PropertyOperator.gt) - q2 = PersonPropertyFilter(key="abc", operator=PropertyOperator.gt) + q1 = EventPropertyFilter(key="abc", operator=PropertyOperator.GT) + q2 = PersonPropertyFilter(key="abc", operator=PropertyOperator.GT) self.assertNotEqual(to_dict(q1), to_dict(q2)) self.assertIn("'type': 'event'", str(to_dict(q1))) @@ -50,7 +50,7 @@ def test_serializes_to_same_json_for_default_value(self): q1 = EventPropertyFilter(key="abc") q2 = EventPropertyFilter(key="abc", operator=None) - q3 = EventPropertyFilter(key="abc", operator=PropertyOperator.exact) + q3 = EventPropertyFilter(key="abc", operator=PropertyOperator.EXACT) self.assertEqual(to_dict(q1), to_dict(q2)) self.assertEqual(to_dict(q2), to_dict(q3)) @@ -149,29 +149,29 @@ def test_serializes_trends_filter(self, f1, f2, num_keys): (None, {}, 0), # general: ordering of keys ( - {"funnelVizType": FunnelVizType.time_to_convert, "funnelOrderType": StepOrderValue.strict}, - {"funnelOrderType": StepOrderValue.strict, "funnelVizType": FunnelVizType.time_to_convert}, + {"funnelVizType": FunnelVizType.TIME_TO_CONVERT, "funnelOrderType": StepOrderValue.STRICT}, + {"funnelOrderType": StepOrderValue.STRICT, "funnelVizType": FunnelVizType.TIME_TO_CONVERT}, 2, ), # binCount # ({}, {"binCount": 4}, 0), ( - {"binCount": 4, "funnelVizType": FunnelVizType.time_to_convert}, - {"binCount": 4, "funnelVizType": FunnelVizType.time_to_convert}, + {"binCount": 4, "funnelVizType": FunnelVizType.TIME_TO_CONVERT}, + {"binCount": 4, "funnelVizType": FunnelVizType.TIME_TO_CONVERT}, 2, ), # breakdownAttributionType - ({}, {"breakdownAttributionType": BreakdownAttributionType.first_touch}, 0), + ({}, {"breakdownAttributionType": BreakdownAttributionType.FIRST_TOUCH}, 0), ( - {"breakdownAttributionType": BreakdownAttributionType.last_touch}, - {"breakdownAttributionType": BreakdownAttributionType.last_touch}, + {"breakdownAttributionType": BreakdownAttributionType.LAST_TOUCH}, + {"breakdownAttributionType": BreakdownAttributionType.LAST_TOUCH}, 1, ), # breakdownAttributionValue # ({}, {"breakdownAttributionValue": 2}, 0), ( - {"breakdownAttributionType": BreakdownAttributionType.step, "breakdownAttributionValue": 2}, - {"breakdownAttributionType": BreakdownAttributionType.step, "breakdownAttributionValue": 2}, + {"breakdownAttributionType": BreakdownAttributionType.STEP, "breakdownAttributionValue": 2}, + {"breakdownAttributionType": BreakdownAttributionType.STEP, "breakdownAttributionValue": 2}, 2, ), # exclusions @@ -187,35 +187,35 @@ def test_serializes_trends_filter(self, f1, f2, num_keys): # funnelFromStep and funnelToStep ({"funnelFromStep": 1, "funnelToStep": 2}, {"funnelFromStep": 1, "funnelToStep": 2}, 2), # funnelOrderType - ({}, {"funnelOrderType": StepOrderValue.ordered}, 0), - ({"funnelOrderType": StepOrderValue.strict}, {"funnelOrderType": StepOrderValue.strict}, 1), + ({}, {"funnelOrderType": StepOrderValue.ORDERED}, 0), + ({"funnelOrderType": StepOrderValue.STRICT}, {"funnelOrderType": StepOrderValue.STRICT}, 1), # funnelStepReference - ({}, {"funnelStepReference": FunnelStepReference.total}, 0), + ({}, {"funnelStepReference": FunnelStepReference.TOTAL}, 0), ( - {"funnelStepReference": FunnelStepReference.previous}, - {"funnelStepReference": FunnelStepReference.previous}, + {"funnelStepReference": FunnelStepReference.PREVIOUS}, + {"funnelStepReference": FunnelStepReference.PREVIOUS}, 1, ), # funnelVizType - ({}, {"funnelVizType": FunnelVizType.steps}, 0), - ({"funnelVizType": FunnelVizType.trends}, {"funnelVizType": FunnelVizType.trends}, 1), + ({}, {"funnelVizType": FunnelVizType.STEPS}, 0), + ({"funnelVizType": FunnelVizType.TRENDS}, {"funnelVizType": FunnelVizType.TRENDS}, 1), # funnelWindowInterval ({}, {"funnelWindowInterval": 14}, 0), ({"funnelWindowInterval": 12}, {"funnelWindowInterval": 12}, 1), # funnelWindowIntervalUnit - ({}, {"funnelWindowIntervalUnit": FunnelConversionWindowTimeUnit.day}, 0), + ({}, {"funnelWindowIntervalUnit": FunnelConversionWindowTimeUnit.DAY}, 0), ( - {"funnelWindowIntervalUnit": FunnelConversionWindowTimeUnit.week}, - {"funnelWindowIntervalUnit": FunnelConversionWindowTimeUnit.week}, + {"funnelWindowIntervalUnit": FunnelConversionWindowTimeUnit.WEEK}, + {"funnelWindowIntervalUnit": FunnelConversionWindowTimeUnit.WEEK}, 1, ), # hidden_legend_breakdowns # ({}, {"hidden_legend_breakdowns": []}, 0), # layout - ({}, {"breakdownAttributionType": BreakdownAttributionType.first_touch}, 0), + ({}, {"breakdownAttributionType": BreakdownAttributionType.FIRST_TOUCH}, 0), ( - {"breakdownAttributionType": BreakdownAttributionType.last_touch}, - {"breakdownAttributionType": BreakdownAttributionType.last_touch}, + {"breakdownAttributionType": BreakdownAttributionType.LAST_TOUCH}, + {"breakdownAttributionType": BreakdownAttributionType.LAST_TOUCH}, 1, ), ] diff --git a/posthog/test/test_team.py b/posthog/test/test_team.py index 1268c476a2adf..44705cac3ca2d 100644 --- a/posthog/test/test_team.py +++ b/posthog/test/test_team.py @@ -139,7 +139,7 @@ def test_team_on_cloud_uses_feature_flag_to_determine_person_on_events(self, moc with override_instance_config("PERSON_ON_EVENTS_ENABLED", False): team = Team.objects.create_with_data(organization=self.organization) self.assertEqual( - team.person_on_events_mode, PersonsOnEventsMode.person_id_override_properties_on_events + team.person_on_events_mode, PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS ) # called more than once when evaluating hogql mock_feature_enabled.assert_called_with( @@ -162,7 +162,7 @@ def test_team_on_self_hosted_uses_instance_setting_to_determine_person_on_events with override_instance_config("PERSON_ON_EVENTS_V2_ENABLED", True): team = Team.objects.create_with_data(organization=self.organization) self.assertEqual( - team.person_on_events_mode, PersonsOnEventsMode.person_id_override_properties_on_events + team.person_on_events_mode, PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS ) for args_list in mock_feature_enabled.call_args_list: # It is ok if we check other feature flags, just not `persons-on-events-v2-reads-enabled` @@ -170,7 +170,7 @@ def test_team_on_self_hosted_uses_instance_setting_to_determine_person_on_events with override_instance_config("PERSON_ON_EVENTS_V2_ENABLED", False): team = Team.objects.create_with_data(organization=self.organization) - self.assertEqual(team.person_on_events_mode, PersonsOnEventsMode.disabled) + self.assertEqual(team.person_on_events_mode, PersonsOnEventsMode.DISABLED) for args_list in mock_feature_enabled.call_args_list: # It is ok if we check other feature flags, just not `persons-on-events-v2-reads-enabled` assert args_list[0][0] != "persons-on-events-v2-reads-enabled" diff --git a/posthog/types.py b/posthog/types.py index 466b537bdea6b..c5b42dd8de896 100644 --- a/posthog/types.py +++ b/posthog/types.py @@ -23,7 +23,7 @@ HogQLPropertyFilter, InsightActorsQuery, PersonPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, SessionPropertyFilter, TrendsQuery, FunnelsQuery, @@ -53,7 +53,7 @@ ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, diff --git a/posthog/warehouse/api/external_data_source.py b/posthog/warehouse/api/external_data_source.py index c53717207f7fd..3b129ed275c97 100644 --- a/posthog/warehouse/api/external_data_source.py +++ b/posthog/warehouse/api/external_data_source.py @@ -12,7 +12,6 @@ from posthog.api.routing import TeamAndOrgViewSetMixin from posthog.warehouse.data_load.service import ( sync_external_data_job_workflow, - trigger_external_data_workflow, delete_external_data_schedule, cancel_external_data_workflow, delete_data_import_folder, @@ -78,8 +77,18 @@ class Meta: "prefix", "last_run_at", "schemas", + "sync_frequency", + ] + read_only_fields = [ + "id", + "created_by", + "created_at", + "status", + "source_type", + "last_run_at", + "schemas", + "prefix", ] - read_only_fields = ["id", "created_by", "created_at", "status", "source_type", "last_run_at", "schemas"] def get_last_run_at(self, instance: ExternalDataSource) -> str: latest_completed_run = ( @@ -116,6 +125,12 @@ def get_schemas(self, instance: ExternalDataSource): schemas = instance.schemas.order_by("name").all() return ExternalDataSchemaSerializer(schemas, many=True, read_only=True, context=self.context).data + def update(self, instance: ExternalDataSource, validated_data: Any) -> Any: + updated_source: ExternalDataSource = super().update(instance, validated_data) + updated_source.update_schemas() + + return updated_source + class SimpleExternalDataSourceSerializers(serializers.ModelSerializer): class Meta: @@ -448,7 +463,7 @@ def destroy(self, request: Request, *args: Any, **kwargs: Any) -> Response: @action(methods=["POST"], detail=True) def reload(self, request: Request, *args: Any, **kwargs: Any): - instance = self.get_object() + instance: ExternalDataSource = self.get_object() if is_any_external_data_job_paused(self.team_id): return Response( @@ -461,17 +476,7 @@ def reload(self, request: Request, *args: Any, **kwargs: Any): except temporalio.service.RPCError: # if the source schedule has been removed - trigger the schema schedules - for schema in ExternalDataSchema.objects.filter( - team_id=self.team_id, source_id=instance.id, should_sync=True - ).all(): - try: - trigger_external_data_workflow(schema) - except temporalio.service.RPCError as e: - if e.status == temporalio.service.RPCStatusCode.NOT_FOUND: - sync_external_data_job_workflow(schema, create=True) - - except Exception as e: - logger.exception(f"Could not trigger external data job for schema {schema.name}", exc_info=e) + instance.reload_schemas() except Exception as e: logger.exception("Could not trigger external data job", exc_info=e) diff --git a/posthog/warehouse/api/table.py b/posthog/warehouse/api/table.py index e460175620fe8..58d6296cb2ae3 100644 --- a/posthog/warehouse/api/table.py +++ b/posthog/warehouse/api/table.py @@ -200,7 +200,7 @@ def update_schema(self, request: request.Request, *args: Any, **kwargs: Any) -> for key, value in updates.items(): try: - DatabaseSerializedFieldType[value] + DatabaseSerializedFieldType[value.upper()] except: return response.Response( status=status.HTTP_400_BAD_REQUEST, diff --git a/posthog/warehouse/api/test/test_external_data_source.py b/posthog/warehouse/api/test/test_external_data_source.py index 1c23807b82328..da7fb63d7e105 100644 --- a/posthog/warehouse/api/test/test_external_data_source.py +++ b/posthog/warehouse/api/test/test_external_data_source.py @@ -6,10 +6,14 @@ from posthog.temporal.data_imports.pipelines.schemas import ( PIPELINE_TYPE_SCHEMA_DEFAULT_MAPPING, ) +from posthog.warehouse.data_load.service import get_sync_schedule from django.test import override_settings from django.conf import settings from posthog.models import Team import psycopg +from rest_framework import status + +import datetime class TestSavedQuery(APIBaseTest): @@ -102,7 +106,17 @@ def test_get_external_data_source_with_schema(self): self.assertEqual(response.status_code, 200) self.assertListEqual( list(payload.keys()), - ["id", "created_at", "created_by", "status", "source_type", "prefix", "last_run_at", "schemas"], + [ + "id", + "created_at", + "created_by", + "status", + "source_type", + "prefix", + "last_run_at", + "schemas", + "sync_frequency", + ], ) self.assertEqual( payload["schemas"], @@ -280,3 +294,36 @@ def test_internal_postgres(self, patch_get_postgres_schemas): ) self.assertEqual(response.status_code, 400) self.assertEqual(response.json(), {"message": "Cannot use internal Postgres database"}) + + @patch("posthog.warehouse.data_load.service.sync_external_data_job_workflow") + def test_update_source_sync_frequency(self, _patch_sync_external_data_job_workflow): + source = self._create_external_data_source() + schema = self._create_external_data_schema(source.pk) + + self.assertEqual(source.sync_frequency, ExternalDataSource.SyncFrequency.DAILY) + # test schedule + schedule = get_sync_schedule(schema) + self.assertEqual( + schedule.spec.intervals[0].every, + datetime.timedelta(days=1), + ) + + # test api + response = self.client.patch( + f"/api/projects/{self.team.id}/external_data_sources/{source.pk}/", + data={"sync_frequency": ExternalDataSource.SyncFrequency.WEEKLY}, + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + source.refresh_from_db() + schema.refresh_from_db() + + self.assertEqual(source.sync_frequency, ExternalDataSource.SyncFrequency.WEEKLY) + self.assertEqual(_patch_sync_external_data_job_workflow.call_count, 1) + + # test schedule + schedule = get_sync_schedule(schema) + self.assertEqual( + schedule.spec.intervals[0].every, + datetime.timedelta(days=7), + ) diff --git a/posthog/warehouse/data_load/service.py b/posthog/warehouse/data_load/service.py index 688ee42b788b5..c3f97406c56cf 100644 --- a/posthog/warehouse/data_load/service.py +++ b/posthog/warehouse/data_load/service.py @@ -46,6 +46,8 @@ def get_sync_schedule(external_data_schema: ExternalDataSchema): external_data_source_id=external_data_schema.source_id, ) + sync_frequency = get_sync_frequency(external_data_schema) + return Schedule( action=ScheduleActionStartWorkflow( "external-data-job", @@ -55,9 +57,7 @@ def get_sync_schedule(external_data_schema: ExternalDataSchema): ), spec=ScheduleSpec( intervals=[ - ScheduleIntervalSpec( - every=timedelta(hours=24), offset=timedelta(hours=external_data_schema.created_at.hour) - ) + ScheduleIntervalSpec(every=sync_frequency, offset=timedelta(hours=external_data_schema.created_at.hour)) ], jitter=timedelta(hours=2), ), @@ -66,6 +66,17 @@ def get_sync_schedule(external_data_schema: ExternalDataSchema): ) +def get_sync_frequency(external_data_schema: ExternalDataSchema): + if external_data_schema.source.sync_frequency == ExternalDataSource.SyncFrequency.DAILY: + return timedelta(days=1) + elif external_data_schema.source.sync_frequency == ExternalDataSource.SyncFrequency.WEEKLY: + return timedelta(weeks=1) + elif external_data_schema.source.sync_frequency == ExternalDataSource.SyncFrequency.MONTHLY: + return timedelta(days=30) + else: + raise ValueError(f"Unknown sync frequency: {external_data_schema.source.sync_frequency}") + + def sync_external_data_job_workflow( external_data_schema: ExternalDataSchema, create: bool = False ) -> ExternalDataSchema: diff --git a/posthog/warehouse/models/external_data_source.py b/posthog/warehouse/models/external_data_source.py index 3b0c8f45aaf6b..58aa891c34db0 100644 --- a/posthog/warehouse/models/external_data_source.py +++ b/posthog/warehouse/models/external_data_source.py @@ -6,6 +6,11 @@ from posthog.warehouse.util import database_sync_to_async from uuid import UUID +import structlog +import temporalio + +logger = structlog.get_logger(__name__) + class ExternalDataSource(CreatedMetaFields, UUIDModel): class Type(models.TextChoices): @@ -22,11 +27,21 @@ class Status(models.TextChoices): COMPLETED = "Completed", "Completed" CANCELLED = "Cancelled", "Cancelled" + class SyncFrequency(models.TextChoices): + DAILY = "day", "Daily" + WEEKLY = "week", "Weekly" + MONTHLY = "month", "Monthly" + # TODO provide flexible schedule definition + source_id: models.CharField = models.CharField(max_length=400) connection_id: models.CharField = models.CharField(max_length=400) destination_id: models.CharField = models.CharField(max_length=400, null=True, blank=True) team: models.ForeignKey = models.ForeignKey(Team, on_delete=models.CASCADE) + sync_frequency: models.CharField = models.CharField( + max_length=128, choices=SyncFrequency.choices, default=SyncFrequency.DAILY, blank=True + ) + # `status` is deprecated in favour of external_data_schema.status status: models.CharField = models.CharField(max_length=400) source_type: models.CharField = models.CharField(max_length=128, choices=Type.choices) @@ -38,6 +53,31 @@ class Status(models.TextChoices): __repr__ = sane_repr("id") + def reload_schemas(self): + from posthog.warehouse.models.external_data_schema import ExternalDataSchema + from posthog.warehouse.data_load.service import sync_external_data_job_workflow, trigger_external_data_workflow + + for schema in ExternalDataSchema.objects.filter( + team_id=self.team.pk, source_id=self.id, should_sync=True + ).all(): + try: + trigger_external_data_workflow(schema) + except temporalio.service.RPCError as e: + if e.status == temporalio.service.RPCStatusCode.NOT_FOUND: + sync_external_data_job_workflow(schema, create=True) + + except Exception as e: + logger.exception(f"Could not trigger external data job for schema {schema.name}", exc_info=e) + + def update_schemas(self): + from posthog.warehouse.models.external_data_schema import ExternalDataSchema + from posthog.warehouse.data_load.service import sync_external_data_job_workflow + + for schema in ExternalDataSchema.objects.filter( + team_id=self.team.pk, source_id=self.id, should_sync=True + ).all(): + sync_external_data_job_workflow(schema, create=False) + @database_sync_to_async def get_external_data_source(source_id: UUID) -> ExternalDataSource: diff --git a/posthog/warehouse/models/external_table_definitions.py b/posthog/warehouse/models/external_table_definitions.py index 7030e3ccb7874..d0e4c57e35c89 100644 --- a/posthog/warehouse/models/external_table_definitions.py +++ b/posthog/warehouse/models/external_table_definitions.py @@ -638,7 +638,3 @@ def get_dlt_mapping_for_external_table(table): for _, field in external_tables[table].items() if type(field) != ast.ExpressionField } - - -def get_imported_fields_for_table(table): - return [field.name for _, field in external_tables[table].items() if type(field) != ast.ExpressionField] diff --git a/posthog/warehouse/models/table.py b/posthog/warehouse/models/table.py index f8893768c03b4..d52adb48b123b 100644 --- a/posthog/warehouse/models/table.py +++ b/posthog/warehouse/models/table.py @@ -35,14 +35,14 @@ from .external_table_definitions import external_tables SERIALIZED_FIELD_TO_CLICKHOUSE_MAPPING: dict[DatabaseSerializedFieldType, str] = { - DatabaseSerializedFieldType.integer: "Int64", - DatabaseSerializedFieldType.float: "Float64", - DatabaseSerializedFieldType.string: "String", - DatabaseSerializedFieldType.datetime: "DateTime64", - DatabaseSerializedFieldType.date: "Date", - DatabaseSerializedFieldType.boolean: "Bool", - DatabaseSerializedFieldType.array: "Array", - DatabaseSerializedFieldType.json: "Map", + DatabaseSerializedFieldType.INTEGER: "Int64", + DatabaseSerializedFieldType.FLOAT: "Float64", + DatabaseSerializedFieldType.STRING: "String", + DatabaseSerializedFieldType.DATETIME: "DateTime64", + DatabaseSerializedFieldType.DATE: "Date", + DatabaseSerializedFieldType.BOOLEAN: "Bool", + DatabaseSerializedFieldType.ARRAY: "Array", + DatabaseSerializedFieldType.JSON: "Map", } CLICKHOUSE_HOGQL_MAPPING = { diff --git a/production.Dockerfile b/production.Dockerfile index 4f58ac88554ec..1e3eb2d11551f 100644 --- a/production.Dockerfile +++ b/production.Dockerfile @@ -315,4 +315,4 @@ EXPOSE 8000 EXPOSE 8001 COPY unit.json.tpl /docker-entrypoint.d/unit.json.tpl USER root -CMD ["./bin/docker"] +CMD ["./bin/docker"] \ No newline at end of file diff --git a/requirements-dev.in b/requirements-dev.in index 691e790df2b35..35ad7044d22dd 100644 --- a/requirements-dev.in +++ b/requirements-dev.in @@ -21,6 +21,7 @@ django-stubs==4.2.7 Faker==17.5.0 fakeredis[lua]==2.11.0 freezegun==1.2.2 +inline-snapshot==0.10.2 packaging==23.1 black~=23.9.1 boto3-stubs[s3] diff --git a/requirements-dev.txt b/requirements-dev.txt index 6a0b72290d43b..ded32c00acb9f 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,5 +1,9 @@ -# This file was autogenerated by uv via the following command: -# uv pip compile requirements-dev.in -o requirements-dev.txt +# +# This file is autogenerated by pip-compile with Python 3.10 +# by the following command: +# +# pip-compile --output-file=requirements-dev.txt requirements-dev.in +# aiohttp==3.9.3 # via # -c requirements.txt @@ -20,6 +24,8 @@ asgiref==3.7.2 # via # -c requirements.txt # django +asttokens==2.4.1 + # via inline-snapshot async-timeout==4.0.2 # via # -c requirements.txt @@ -33,10 +39,10 @@ attrs==23.2.0 # referencing black==23.9.1 # via - # -c requirements.txt # -r requirements-dev.in # datamodel-code-generator -boto3-stubs==1.34.84 + # inline-snapshot +boto3-stubs[s3]==1.34.84 # via -r requirements-dev.in botocore-stubs==1.34.84 # via boto3-stubs @@ -58,9 +64,10 @@ click==8.1.7 # via # -c requirements.txt # black + # inline-snapshot colorama==0.4.4 # via pytest-watch -coverage==5.5 +coverage[toml]==5.5 # via pytest-cov cryptography==37.0.2 # via @@ -95,9 +102,11 @@ exceptiongroup==1.2.1 # pytest execnet==2.1.1 # via pytest-xdist +executing==2.0.1 + # via inline-snapshot faker==17.5.0 # via -r requirements-dev.in -fakeredis==2.11.0 +fakeredis[lua]==2.11.0 # via -r requirements-dev.in flaky==3.7.0 # via -r requirements-dev.in @@ -122,6 +131,8 @@ inflect==5.6.2 # via datamodel-code-generator iniconfig==1.1.1 # via pytest +inline-snapshot==0.10.2 + # via -r requirements-dev.in isort==5.2.2 # via datamodel-code-generator jinja2==3.1.4 @@ -142,8 +153,12 @@ lazy-object-proxy==1.10.0 # via openapi-spec-validator lupa==1.14.1 # via fakeredis +markdown-it-py==3.0.0 + # via rich markupsafe==2.1.5 # via jinja2 +mdurl==0.1.2 + # via markdown-it-py multidict==6.0.2 # via # -c requirements.txt @@ -157,7 +172,6 @@ mypy-boto3-s3==1.34.65 # via boto3-stubs mypy-extensions==1.0.0 # via - # -c requirements.txt # -r requirements-dev.in # black # mypy @@ -178,9 +192,7 @@ parameterized==0.9.0 pathable==0.4.3 # via jsonschema-path pathspec==0.12.1 - # via - # -c requirements.txt - # black + # via black platformdirs==3.11.0 # via # -c requirements.txt @@ -195,7 +207,7 @@ pycparser==2.20 # via # -c requirements.txt # cffi -pydantic==2.5.3 +pydantic[email]==2.5.3 # via # -c requirements.txt # datamodel-code-generator @@ -203,6 +215,8 @@ pydantic-core==2.14.6 # via # -c requirements.txt # pydantic +pygments==2.18.0 + # via rich pytest==7.4.4 # via # -r requirements-dev.in @@ -267,6 +281,8 @@ responses==0.23.1 # via -r requirements-dev.in rfc3339-validator==0.1.4 # via openapi-schema-validator +rich==13.7.1 + # via inline-snapshot rpds-py==0.16.2 # via # -c requirements.txt @@ -281,6 +297,7 @@ ruff==0.4.3 six==1.16.0 # via # -c requirements.txt + # asttokens # prance # python-dateutil # rfc3339-validator @@ -294,13 +311,13 @@ sqlparse==0.4.4 # django syrupy==4.6.0 # via -r requirements-dev.in -toml==0.10.1 +toml==0.10.2 # via # coverage # datamodel-code-generator + # inline-snapshot tomli==2.0.1 # via - # -c requirements.txt # black # django-stubs # mypy @@ -336,6 +353,8 @@ types-retry==0.9.9.4 # via -r requirements-dev.in types-s3transfer==0.10.1 # via boto3-stubs +types-toml==0.10.8.20240310 + # via inline-snapshot types-tzlocal==5.1.0.1 # via -r requirements-dev.in typing-extensions==4.7.1 diff --git a/requirements.in b/requirements.in index 72dba7f70ba2e..2b7efd33c6ddb 100644 --- a/requirements.in +++ b/requirements.in @@ -93,5 +93,5 @@ phonenumberslite==8.13.6 openai==1.10.0 tiktoken==0.6.0 nh3==0.2.14 -hogql-parser==1.0.11 +hogql-parser==1.0.12 zxcvbn==4.4.28 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index dcc57107014fb..3ab55989b8abe 100644 --- a/requirements.txt +++ b/requirements.txt @@ -276,7 +276,7 @@ h11==0.13.0 # wsproto hexbytes==1.0.0 # via dlt -hogql-parser==1.0.11 +hogql-parser==1.0.12 # via -r requirements.in httpcore==1.0.2 # via httpx diff --git a/rust/.github/workflows/rust.yml b/rust/.github/workflows/rust.yml deleted file mode 100644 index ff028773ab724..0000000000000 --- a/rust/.github/workflows/rust.yml +++ /dev/null @@ -1,105 +0,0 @@ -name: Rust - -on: - workflow_dispatch: - push: - branches: [main] - pull_request: - -env: - CARGO_TERM_COLOR: always - -jobs: - build: - runs-on: depot-ubuntu-22.04-4 - - steps: - - uses: actions/checkout@v3 - - - name: Install rust - uses: dtolnay/rust-toolchain@1.77 - - - uses: actions/cache@v3 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - target - key: ${{ runner.os }}-cargo-release-${{ hashFiles('**/Cargo.lock') }} - - - name: Run cargo build - run: cargo build --all --locked --release && find target/release/ -maxdepth 1 -executable -type f | xargs strip - - test: - runs-on: depot-ubuntu-22.04-4 - timeout-minutes: 10 - - steps: - - uses: actions/checkout@v3 - - - name: Login to DockerHub - uses: docker/login-action@v2 - with: - username: posthog - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Setup dependencies - run: | - docker compose up kafka redis db echo_server -d --wait - docker compose up setup_test_db - echo "127.0.0.1 kafka" | sudo tee -a /etc/hosts - - - name: Install rust - uses: dtolnay/rust-toolchain@1.77 - - - uses: actions/cache@v3 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - target - key: ${ runner.os }-cargo-debug-${{ hashFiles('**/Cargo.lock') }} - - - name: Run cargo test - run: cargo test --all-features - - linting: - runs-on: depot-ubuntu-22.04-4 - - steps: - - uses: actions/checkout@v3 - - - name: Install rust - uses: dtolnay/rust-toolchain@1.77 - with: - components: clippy,rustfmt - - - uses: actions/cache@v3 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - target - key: ${{ runner.os }}-cargo-debug-${{ hashFiles('**/Cargo.lock') }} - - - name: Check format - run: cargo fmt -- --check - - - name: Run clippy - run: cargo clippy -- -D warnings - - - name: Run cargo check - run: cargo check --all-features - - shear: - runs-on: depot-ubuntu-22.04-4 - steps: - - uses: actions/checkout@v3 - - - name: Install cargo-binstall - uses: cargo-bins/cargo-binstall@main - - - name: Install cargo-shear - run: cargo binstall --no-confirm cargo-shear - - - run: cargo shear diff --git a/rust/Dockerfile.migrate b/rust/Dockerfile.migrate index 47790778f4396..e7fc120360b79 100644 --- a/rust/Dockerfile.migrate +++ b/rust/Dockerfile.migrate @@ -6,8 +6,8 @@ RUN cargo install sqlx-cli@0.7.3 --no-default-features --features native-tls,pos FROM debian:bullseye-20230320-slim AS runtime WORKDIR /sqlx -ADD bin /sqlx/bin/ -ADD migrations /sqlx/migrations/ +COPY bin /sqlx/bin/ +COPY migrations /sqlx/migrations/ COPY --from=builder /app/target/release/bin/sqlx /usr/local/bin diff --git a/rust/depot.json b/rust/depot.json index 24c71eb973113..316dd6ef94564 100644 --- a/rust/depot.json +++ b/rust/depot.json @@ -1 +1 @@ -{ "id": "zcszdgwzsw" } +{ "id": "x19jffd9zf" }