From 76efa0baa0a5dbe7d61bc6fa047c6860855bac25 Mon Sep 17 00:00:00 2001 From: Tom Owers Date: Fri, 25 Oct 2024 16:59:29 +0100 Subject: [PATCH 1/3] chore(data-warehouse): Added more reasons for jobs to not retry (#25785) --- mypy-baseline.txt | 1 - .../data_imports/external_data_job.py | 49 +++++++++++++---- .../tests/data_imports/test_end_to_end.py | 54 +++++++++++++++++++ .../external_data/test_external_data_job.py | 7 ++- 4 files changed, 99 insertions(+), 12 deletions(-) diff --git a/mypy-baseline.txt b/mypy-baseline.txt index 6f5de8a4ccf38..57a7e095f6298 100644 --- a/mypy-baseline.txt +++ b/mypy-baseline.txt @@ -808,7 +808,6 @@ posthog/temporal/tests/external_data/test_external_data_job.py:0: error: Invalid posthog/temporal/tests/external_data/test_external_data_job.py:0: error: Invalid index type "str" for "dict[Type, Sequence[str]]"; expected type "Type" [index] posthog/temporal/tests/external_data/test_external_data_job.py:0: error: Invalid index type "str" for "dict[Type, Sequence[str]]"; expected type "Type" [index] posthog/temporal/tests/external_data/test_external_data_job.py:0: error: Invalid index type "str" for "dict[Type, Sequence[str]]"; expected type "Type" [index] -posthog/temporal/tests/external_data/test_external_data_job.py:0: error: Invalid index type "str" for "dict[Type, Sequence[str]]"; expected type "Type" [index] posthog/api/test/batch_exports/conftest.py:0: error: Signature of "run" incompatible with supertype "Worker" [override] posthog/api/test/batch_exports/conftest.py:0: note: Superclass: posthog/api/test/batch_exports/conftest.py:0: note: def run(self) -> Coroutine[Any, Any, None] diff --git a/posthog/temporal/data_imports/external_data_job.py b/posthog/temporal/data_imports/external_data_job.py index 1f44942a422b7..01367cb710812 100644 --- a/posthog/temporal/data_imports/external_data_job.py +++ b/posthog/temporal/data_imports/external_data_job.py @@ -2,6 +2,7 @@ import datetime as dt import json +import posthoganalytics from temporalio import activity, exceptions, workflow from temporalio.common import RetryPolicy @@ -21,6 +22,7 @@ create_external_data_job_model_activity, ) from posthog.temporal.data_imports.workflow_activities.import_data import ImportDataActivityInputs, import_data_activity +from posthog.utils import get_machine_id from posthog.warehouse.data_load.service import ( a_delete_external_data_schedule, a_external_data_workflow_exists, @@ -37,16 +39,27 @@ ExternalDataJob, get_active_schemas_for_source_id, ExternalDataSource, + get_external_data_source, ) from posthog.temporal.common.logger import bind_temporal_worker_logger from posthog.warehouse.models.external_data_schema import aupdate_should_sync -Non_Retryable_Schema_Errors = [ - "NoSuchTableError", - "401 Client Error: Unauthorized for url: https://api.stripe.com", - "403 Client Error: Forbidden for url: https://api.stripe.com", -] +Non_Retryable_Schema_Errors: dict[ExternalDataSource.Type, list[str]] = { + ExternalDataSource.Type.STRIPE: [ + "401 Client Error: Unauthorized for url: https://api.stripe.com", + "403 Client Error: Forbidden for url: https://api.stripe.com", + ], + ExternalDataSource.Type.POSTGRES: [ + "NoSuchTableError", + "is not permitted to log in", + "Tenant or user not found connection to server", + "FATAL: Tenant or user not found", + "error received from server in SCRAM exchange: Wrong password", + "could not translate host name", + ], + ExternalDataSource.Type.ZENDESK: ["404 Client Error: Not Found for url", "403 Client Error: Forbidden for url"], +} @dataclasses.dataclass @@ -54,6 +67,7 @@ class UpdateExternalDataJobStatusInputs: team_id: int job_id: str | None schema_id: str + source_id: str status: str internal_error: str | None latest_error: str | None @@ -78,10 +92,26 @@ async def update_external_data_job_model(inputs: UpdateExternalDataJobStatusInpu f"External data job failed for external data schema {inputs.schema_id} with error: {inputs.internal_error}" ) - has_non_retryable_error = any(error in inputs.internal_error for error in Non_Retryable_Schema_Errors) - if has_non_retryable_error: - logger.info("Schema has a non-retryable error - turning off syncing") - await aupdate_should_sync(schema_id=inputs.schema_id, team_id=inputs.team_id, should_sync=False) + source: ExternalDataSource = await get_external_data_source(inputs.source_id) + non_retryable_errors = Non_Retryable_Schema_Errors.get(ExternalDataSource.Type(source.source_type)) + + if non_retryable_errors is not None: + has_non_retryable_error = any(error in inputs.internal_error for error in non_retryable_errors) + if has_non_retryable_error: + logger.info("Schema has a non-retryable error - turning off syncing") + posthoganalytics.capture( + get_machine_id(), + "schema non-retryable error", + { + "schemaId": inputs.schema_id, + "sourceId": inputs.source_id, + "sourceType": source.source_type, + "jobId": inputs.job_id, + "teamId": inputs.team_id, + "error": inputs.internal_error, + }, + ) + await aupdate_should_sync(schema_id=inputs.schema_id, team_id=inputs.team_id, should_sync=False) await aupdate_external_job_status( job_id=job_id, @@ -166,6 +196,7 @@ async def run(self, inputs: ExternalDataWorkflowInputs): internal_error=None, team_id=inputs.team_id, schema_id=str(inputs.external_data_schema_id), + source_id=str(inputs.external_data_source_id), ) try: diff --git a/posthog/temporal/tests/data_imports/test_end_to_end.py b/posthog/temporal/tests/data_imports/test_end_to_end.py index cb92492b29c4c..98d81cc153bbe 100644 --- a/posthog/temporal/tests/data_imports/test_end_to_end.py +++ b/posthog/temporal/tests/data_imports/test_end_to_end.py @@ -6,6 +6,7 @@ from asgiref.sync import sync_to_async from django.conf import settings from django.test import override_settings +import posthoganalytics import pytest import pytest_asyncio import psycopg @@ -905,3 +906,56 @@ def get_jobs(): with pytest.raises(Exception): await sync_to_async(execute_hogql_query)("SELECT * FROM stripe_customer", team) + + +@pytest.mark.django_db(transaction=True) +@pytest.mark.asyncio +async def test_non_retryable_error(team, stripe_customer): + source = await sync_to_async(ExternalDataSource.objects.create)( + source_id=uuid.uuid4(), + connection_id=uuid.uuid4(), + destination_id=uuid.uuid4(), + team=team, + status="running", + source_type="Stripe", + job_inputs={"stripe_secret_key": "test-key", "stripe_account_id": "acct_id"}, + ) + + schema = await sync_to_async(ExternalDataSchema.objects.create)( + name="Customer", + team_id=team.pk, + source_id=source.pk, + sync_type=ExternalDataSchema.SyncType.FULL_REFRESH, + sync_type_config={}, + ) + + workflow_id = str(uuid.uuid4()) + inputs = ExternalDataWorkflowInputs( + team_id=team.id, + external_data_source_id=source.pk, + external_data_schema_id=schema.id, + ) + + with ( + mock.patch( + "posthog.temporal.data_imports.workflow_activities.check_billing_limits.list_limited_team_attributes", + ) as mock_list_limited_team_attributes, + mock.patch.object(posthoganalytics, "capture") as capture_mock, + ): + mock_list_limited_team_attributes.side_effect = Exception( + "401 Client Error: Unauthorized for url: https://api.stripe.com" + ) + + with pytest.raises(Exception): + await _execute_run(workflow_id, inputs, stripe_customer["data"]) + + capture_mock.assert_called_once() + + job: ExternalDataJob = await sync_to_async(ExternalDataJob.objects.get)(team_id=team.id, schema_id=schema.pk) + await sync_to_async(schema.refresh_from_db)() + + assert job.status == ExternalDataJob.Status.FAILED + assert schema.should_sync is False + + with pytest.raises(Exception): + await sync_to_async(execute_hogql_query)("SELECT * FROM stripe_customer", team) 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 d5839cd4ce0ef..1b5b3d692d5ed 100644 --- a/posthog/temporal/tests/external_data/test_external_data_job.py +++ b/posthog/temporal/tests/external_data/test_external_data_job.py @@ -252,6 +252,7 @@ async def test_update_external_job_activity(activity_environment, team, **kwargs latest_error=None, internal_error=None, schema_id=str(schema.pk), + source_id=str(new_source.pk), team_id=team.id, ) @@ -296,6 +297,7 @@ async def test_update_external_job_activity_with_retryable_error(activity_enviro latest_error=None, internal_error="Some other retryable error", schema_id=str(schema.pk), + source_id=str(new_source.pk), team_id=team.id, ) @@ -317,11 +319,11 @@ async def test_update_external_job_activity_with_non_retryable_error(activity_en destination_id=uuid.uuid4(), team=team, status="running", - source_type="Stripe", + source_type="Postgres", ) schema = await sync_to_async(ExternalDataSchema.objects.create)( - name=PIPELINE_TYPE_SCHEMA_DEFAULT_MAPPING[new_source.source_type][0], + name="test_123", team_id=team.id, source_id=new_source.pk, should_sync=True, @@ -341,6 +343,7 @@ async def test_update_external_job_activity_with_non_retryable_error(activity_en latest_error=None, internal_error="NoSuchTableError: TableA", schema_id=str(schema.pk), + source_id=str(new_source.pk), team_id=team.id, ) with mock.patch("posthog.warehouse.models.external_data_schema.external_data_workflow_exists", return_value=False): From 0ad1b5fe3b779d5518772404fd5e73084efb5d92 Mon Sep 17 00:00:00 2001 From: Patricio Date: Fri, 25 Oct 2024 14:33:58 -0300 Subject: [PATCH 2/3] fix: link to project home in env switcher when in onboarding (#25799) Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- ...er--trends-line-insight-detailed--dark.png | Bin 21655 -> 21701 bytes ...r--trends-line-insight-detailed--light.png | Bin 21344 -> 21385 bytes .../layout/navigation/EnvironmentSwitcher.tsx | 11 +++++++++++ 3 files changed, 11 insertions(+) diff --git a/frontend/__snapshots__/exporter-exporter--trends-line-insight-detailed--dark.png b/frontend/__snapshots__/exporter-exporter--trends-line-insight-detailed--dark.png index e3e93b3dd9678360493d495ab090800a9f994b6f..b54d4facb0bfe8353e19863b17eee837e4aab6c0 100644 GIT binary patch literal 21701 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 RdnN$nb5B=4mvv4FO#tji&>jE) literal 21655 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 diff --git a/frontend/src/layout/navigation/EnvironmentSwitcher.tsx b/frontend/src/layout/navigation/EnvironmentSwitcher.tsx index 2432cb72c63f7..83c85092ff3ab 100644 --- a/frontend/src/layout/navigation/EnvironmentSwitcher.tsx +++ b/frontend/src/layout/navigation/EnvironmentSwitcher.tsx @@ -157,6 +157,17 @@ function determineProjectSwitchUrl(pathname: string, newTeamId: number): string // and after switching is on a different page than before. let route = removeProjectIdIfPresent(pathname) route = removeFlagIdIfPresent(route) + + // List of routes that should redirect to project home + // instead of keeping the current path. + const redirectToHomeRoutes = ['/products', '/onboarding'] + + const shouldRedirectToHome = redirectToHomeRoutes.some((redirectRoute) => route.includes(redirectRoute)) + + if (shouldRedirectToHome) { + return urls.project(newTeamId) // Go to project home + } + return urls.project(newTeamId, route) } From dff2676253c2c4b552e84812f2320dc5c4eab516 Mon Sep 17 00:00:00 2001 From: timgl Date: Fri, 25 Oct 2024 19:23:55 +0100 Subject: [PATCH 3/3] perf: Speed up selecting from persons table (#25824) Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- .../api/test/__snapshots__/test_api_docs.ambr | 2 +- posthog/hogql/database/schema/persons.py | 44 +++++- .../test/__snapshots__/test_persons.ambr | 58 ++++++++ .../database/schema/test/test_persons.py | 128 ++++++++++++++++++ .../hogql/test/__snapshots__/test_query.ambr | 15 +- 5 files changed, 235 insertions(+), 12 deletions(-) create mode 100644 posthog/hogql/database/schema/test/__snapshots__/test_persons.ambr create mode 100644 posthog/hogql/database/schema/test/test_persons.py diff --git a/posthog/api/test/__snapshots__/test_api_docs.ambr b/posthog/api/test/__snapshots__/test_api_docs.ambr index 6ef31c6530176..a5f9b394809ae 100644 --- a/posthog/api/test/__snapshots__/test_api_docs.ambr +++ b/posthog/api/test/__snapshots__/test_api_docs.ambr @@ -97,8 +97,8 @@ '/home/runner/work/posthog/posthog/posthog/api/survey.py: Warning [SurveyViewSet > SurveySerializer]: unable to resolve type hint for function "get_conditions". Consider using a type hint or @extend_schema_field. Defaulting to string.', '/home/runner/work/posthog/posthog/posthog/api/web_experiment.py: Warning [WebExperimentViewSet]: could not derive type of path parameter "project_id" because model "posthog.models.web_experiment.WebExperiment" contained no such field. Consider annotating parameter with @extend_schema. Defaulting to "string".', 'Warning: encountered multiple names for the same choice set (HrefMatchingEnum). This may be unwanted even though the generated schema is technically correct. Add an entry to ENUM_NAME_OVERRIDES to fix the naming.', - 'Warning: enum naming encountered a non-optimally resolvable collision for fields named "kind". The same name has been used for multiple choice sets in multiple components. The collision was resolved with "Kind069Enum". add an entry to ENUM_NAME_OVERRIDES to fix the naming.', 'Warning: enum naming encountered a non-optimally resolvable collision for fields named "kind". The same name has been used for multiple choice sets in multiple components. The collision was resolved with "KindCfaEnum". add an entry to ENUM_NAME_OVERRIDES to fix the naming.', + 'Warning: enum naming encountered a non-optimally resolvable collision for fields named "kind". The same name has been used for multiple choice sets in multiple components. The collision was resolved with "Kind069Enum". add an entry to ENUM_NAME_OVERRIDES to fix the naming.', 'Warning: enum naming encountered a non-optimally resolvable collision for fields named "type". The same name has been used for multiple choice sets in multiple components. The collision was resolved with "TypeF73Enum". add an entry to ENUM_NAME_OVERRIDES to fix the naming.', 'Warning: encountered multiple names for the same choice set (EffectivePrivilegeLevelEnum). This may be unwanted even though the generated schema is technically correct. Add an entry to ENUM_NAME_OVERRIDES to fix the naming.', 'Warning: encountered multiple names for the same choice set (MembershipLevelEnum). This may be unwanted even though the generated schema is technically correct. Add an entry to ENUM_NAME_OVERRIDES to fix the naming.', diff --git a/posthog/hogql/database/schema/persons.py b/posthog/hogql/database/schema/persons.py index 0b0747593b7a7..04e8eaa47dd5e 100644 --- a/posthog/hogql/database/schema/persons.py +++ b/posthog/hogql/database/schema/persons.py @@ -1,10 +1,12 @@ from typing import cast, Optional, Self +from posthog.hogql.parser import parse_select import posthoganalytics from posthog.hogql.ast import SelectQuery, And, CompareOperation, CompareOperationOp, Field, JoinExpr from posthog.hogql.base import Expr from posthog.hogql.constants import HogQLQuerySettings from posthog.hogql.context import HogQLContext +from posthog.hogql import ast from posthog.hogql.database.argmax import argmax_select from posthog.hogql.database.models import ( BooleanDatabaseField, @@ -56,10 +58,46 @@ def select_from_persons_table( version = PersonsArgMaxVersion.V2 break - if version == PersonsArgMaxVersion.V2: - from posthog.hogql import ast - from posthog.hogql.parser import parse_select + and_conditions = [] + + if filter is not None: + and_conditions.append(filter) + # For now, only do this optimization for directly querying the persons table (without joins or as part of a subquery) to avoid knock-on effects to insight queries + if ( + node.select_from + and node.select_from.type + and hasattr(node.select_from.type, "table") + and node.select_from.type.table + and isinstance(node.select_from.type.table, PersonsTable) + ): + extractor = WhereClauseExtractor(context) + extractor.add_local_tables(join_or_table) + where = extractor.get_inner_where(node) + if where: + select = argmax_select( + table_name="raw_persons", + select_fields=join_or_table.fields_accessed, + group_fields=["id"], + argmax_field="version", + deleted_field="is_deleted", + timestamp_field_to_clamp="created_at", + ) + inner_select = cast( + ast.SelectQuery, + parse_select( + """ + SELECT id FROM raw_persons as where_optimization + """ + ), + ) + inner_select.where = where + select.where = ast.CompareOperation( + left=ast.Field(chain=["id"]), right=inner_select, op=ast.CompareOperationOp.In + ) + return select + + if version == PersonsArgMaxVersion.V2: select = cast( ast.SelectQuery, parse_select( diff --git a/posthog/hogql/database/schema/test/__snapshots__/test_persons.ambr b/posthog/hogql/database/schema/test/__snapshots__/test_persons.ambr new file mode 100644 index 0000000000000..90d7cb108ef8d --- /dev/null +++ b/posthog/hogql/database/schema/test/__snapshots__/test_persons.ambr @@ -0,0 +1,58 @@ +# serializer version: 1 +# name: TestPersonOptimization.test_joins_are_left_alone_for_now + ''' + SELECT events.uuid AS uuid + FROM events + INNER JOIN + (SELECT argMax(person_distinct_id2.person_id, person_distinct_id2.version) AS events__pdi___person_id, + argMax(person_distinct_id2.person_id, person_distinct_id2.version) AS person_id, + person_distinct_id2.distinct_id AS distinct_id + FROM person_distinct_id2 + WHERE equals(person_distinct_id2.team_id, 2) + GROUP BY person_distinct_id2.distinct_id + HAVING ifNull(equals(argMax(person_distinct_id2.is_deleted, person_distinct_id2.version), 0), 0) SETTINGS optimize_aggregation_in_order=1) AS events__pdi ON equals(events.distinct_id, events__pdi.distinct_id) + INNER JOIN + (SELECT person.id AS id, + replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person.properties, '$some_prop'), ''), 'null'), '^"|"$', '') AS `properties___$some_prop` + FROM person + WHERE and(equals(person.team_id, 2), ifNull(in(tuple(person.id, person.version), + (SELECT person.id AS id, max(person.version) AS version + FROM person + WHERE equals(person.team_id, 2) + GROUP BY person.id + HAVING and(ifNull(equals(argMax(person.is_deleted, person.version), 0), 0), ifNull(less(argMax(toTimeZone(person.created_at, 'UTC'), person.version), plus(now64(6, 'UTC'), toIntervalDay(1))), 0)))), 0)) SETTINGS optimize_aggregation_in_order=1) AS events__pdi__person ON equals(events__pdi.events__pdi___person_id, events__pdi__person.id) + WHERE and(equals(events.team_id, 2), ifNull(equals(events__pdi__person.`properties___$some_prop`, 'something'), 0)) + LIMIT 100 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0 + ''' +# --- +# name: TestPersonOptimization.test_simple_filter + ''' + SELECT persons.id AS id, + persons.properties AS properties + FROM + (SELECT argMax(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person.properties, '$some_prop'), ''), 'null'), '^"|"$', ''), person.version) AS `properties___$some_prop`, + argMax(person.properties, person.version) AS properties, + person.id AS id + FROM person + WHERE and(equals(person.team_id, 2), in(id, + (SELECT where_optimization.id AS id + FROM person AS where_optimization + WHERE and(equals(where_optimization.team_id, 2), ifNull(equals(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(where_optimization.properties, '$some_prop'), ''), 'null'), '^"|"$', ''), 'something'), 0))))) + GROUP BY person.id + HAVING and(ifNull(equals(argMax(person.is_deleted, person.version), 0), 0), ifNull(less(argMax(toTimeZone(person.created_at, 'UTC'), person.version), plus(now64(6, 'UTC'), toIntervalDay(1))), 0))) AS persons + WHERE ifNull(equals(persons.`properties___$some_prop`, 'something'), 0) + LIMIT 100 SETTINGS readonly=2, + max_execution_time=60, + allow_experimental_object_type=1, + format_csv_allow_double_quotes=0, + max_ast_elements=4000000, + max_expanded_ast_elements=4000000, + max_bytes_before_external_group_by=0 + ''' +# --- diff --git a/posthog/hogql/database/schema/test/test_persons.py b/posthog/hogql/database/schema/test/test_persons.py new file mode 100644 index 0000000000000..e259df691d06e --- /dev/null +++ b/posthog/hogql/database/schema/test/test_persons.py @@ -0,0 +1,128 @@ +from posthog.hogql.parser import parse_select +from posthog.schema import ( + PersonsOnEventsMode, + InsightActorsQuery, + TrendsQuery, + ActorsQuery, + EventsNode, + InsightDateRange, +) +from posthog.hogql_queries.actors_query_runner import ActorsQueryRunner +from posthog.hogql.modifiers import create_default_modifiers_for_team +from posthog.hogql.query import execute_hogql_query +from posthog.test.base import ( + APIBaseTest, + ClickhouseTestMixin, + _create_person, + _create_event, + snapshot_clickhouse_queries, +) +from posthog.models.person.util import create_person +from datetime import datetime + +from unittest.mock import patch, Mock + + +@patch("posthoganalytics.feature_enabled", new=Mock(return_value=True)) # for persons-inner-where-optimization +class TestPersonOptimization(ClickhouseTestMixin, APIBaseTest): + """ + Mostly tests for the optimization of pre-filtering before aggregating. See https://github.com/PostHog/posthog/pull/25604 + """ + + def setUp(self): + super().setUp() + self.first_person = _create_person( + team_id=self.team.pk, + distinct_ids=["1"], + properties={"$some_prop": "something", "$another_prop": "something1"}, + created_at=datetime(2024, 1, 1, 12), + ) + self.second_person = _create_person( + team_id=self.team.pk, + properties={"$some_prop": "ifwematcholdversionsthiswillmatch", "$another_prop": "something2"}, + distinct_ids=["2"], + version=1, + created_at=datetime(2024, 1, 1, 13), + ) + # update second_person with the correct prop + create_person( + team_id=self.team.pk, + uuid=str(self.second_person.uuid), + properties={"$some_prop": "something", "$another_prop": "something2"}, + created_at=datetime(2024, 1, 1, 13), + version=2, + ) + self.third_person = _create_person( + team_id=self.team.pk, + distinct_ids=["3"], + properties={"$some_prop": "not something", "$another_prop": "something3"}, + created_at=datetime(2024, 1, 1, 14), + ) + # deleted + self.deleted_person = _create_person( + team_id=self.team.pk, + properties={"$some_prop": "ifwematcholdversionsthiswillmatch", "$another_prop": "something2"}, + distinct_ids=["deleted"], + created_at=datetime(2024, 1, 1, 13), + version=1, + ) + create_person(team_id=self.team.pk, uuid=str(self.deleted_person.uuid), version=2, is_deleted=True) + _create_event(event="$pageview", distinct_id="1", team=self.team) + _create_event(event="$pageview", distinct_id="2", team=self.team) + _create_event(event="$pageview", distinct_id="3", team=self.team) + self.modifiers = create_default_modifiers_for_team(self.team) + self.modifiers.personsOnEventsMode = PersonsOnEventsMode.DISABLED + # self.modifiers.optimizeJoinedFilters = True + # self.modifiers.personsArgMaxVersion = PersonsArgMaxVersion.V1 + + @snapshot_clickhouse_queries + def test_simple_filter(self): + response = execute_hogql_query( + parse_select("select id, properties from persons where properties.$some_prop = 'something'"), + self.team, + modifiers=self.modifiers, + ) + assert len(response.results) == 2 + assert response.clickhouse + self.assertIn("where_optimization", response.clickhouse) + self.assertNotIn("in(tuple(person.id, person.version)", response.clickhouse) + + @snapshot_clickhouse_queries + def test_joins_are_left_alone_for_now(self): + response = execute_hogql_query( + parse_select("select uuid from events where person.properties.$some_prop = 'something'"), + self.team, + modifiers=self.modifiers, + ) + assert len(response.results) == 2 + assert response.clickhouse + self.assertIn("in(tuple(person.id, person.version)", response.clickhouse) + self.assertNotIn("where_optimization", response.clickhouse) + + def test_person_modal_not_optimized_yet(self): + source_query = TrendsQuery( + series=[EventsNode(event="$pageview")], + dateRange=InsightDateRange(date_from="2024-01-01", date_to="2024-01-07"), + # breakdownFilter=BreakdownFilter(breakdown="$", breakdown_type=BreakdownType.PERSON), + ) + insight_actors_query = InsightActorsQuery( + source=source_query, + day="2024-01-01", + modifiers=self.modifiers, + ) + actors_query = ActorsQuery( + source=insight_actors_query, + offset=0, + select=[ + "actor", + "created_at", + "event_count", + # "matched_recordings", + ], + orderBy=["event_count DESC"], + modifiers=self.modifiers, + ) + query_runner = ActorsQueryRunner(query=actors_query, team=self.team) + response = execute_hogql_query(query_runner.to_query(), self.team, modifiers=self.modifiers) + assert response.clickhouse + self.assertNotIn("where_optimization", response.clickhouse) diff --git a/posthog/hogql/test/__snapshots__/test_query.ambr b/posthog/hogql/test/__snapshots__/test_query.ambr index 96bfad37a5a50..d41d6417cdbd4 100644 --- a/posthog/hogql/test/__snapshots__/test_query.ambr +++ b/posthog/hogql/test/__snapshots__/test_query.ambr @@ -459,16 +459,15 @@ SELECT DISTINCT persons.properties___sneaky_mail AS sneaky_mail FROM ( - SELECT person.id AS id, replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person.properties, %(hogql_val_0)s), ''), 'null'), '^"|"$', '') AS properties___sneaky_mail, replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person.properties, %(hogql_val_1)s), ''), 'null'), '^"|"$', '') AS properties___random_uuid + SELECT argMax(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person.properties, %(hogql_val_0)s), ''), 'null'), '^"|"$', ''), person.version) AS properties___sneaky_mail, argMax(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person.properties, %(hogql_val_1)s), ''), 'null'), '^"|"$', ''), person.version) AS properties___random_uuid, person.id AS id FROM person - WHERE and(equals(person.team_id, 420), ifNull(in(tuple(person.id, person.version), ( - SELECT person.id AS id, max(person.version) AS version - FROM person - WHERE equals(person.team_id, 420) + WHERE and(equals(person.team_id, 420), in(id, ( + SELECT where_optimization.id AS id + FROM person AS where_optimization + WHERE and(equals(where_optimization.team_id, 420), ifNull(equals(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(where_optimization.properties, %(hogql_val_2)s), ''), 'null'), '^"|"$', ''), %(hogql_val_3)s), 0))))) GROUP BY person.id - HAVING and(ifNull(equals(argMax(person.is_deleted, person.version), 0), 0), ifNull(less(argMax(toTimeZone(person.created_at, %(hogql_val_2)s), person.version), plus(now64(6, %(hogql_val_3)s), toIntervalDay(1))), 0)))), 0)) - SETTINGS optimize_aggregation_in_order=1) AS persons - WHERE ifNull(equals(persons.properties___random_uuid, %(hogql_val_4)s), 0) + HAVING and(ifNull(equals(argMax(person.is_deleted, person.version), 0), 0), ifNull(less(argMax(toTimeZone(person.created_at, %(hogql_val_4)s), person.version), plus(now64(6, %(hogql_val_5)s), toIntervalDay(1))), 0))) AS persons + WHERE ifNull(equals(persons.properties___random_uuid, %(hogql_val_6)s), 0) LIMIT 100 SETTINGS readonly=2, max_execution_time=60, allow_experimental_object_type=1, format_csv_allow_double_quotes=0, max_ast_elements=4000000, max_expanded_ast_elements=4000000, max_bytes_before_external_group_by=0