diff --git a/frontend/src/queries/schema.json b/frontend/src/queries/schema.json index c626b70d76d06..d1eed070437ff 100644 --- a/frontend/src/queries/schema.json +++ b/frontend/src/queries/schema.json @@ -1541,38 +1541,51 @@ "additionalProperties": false, "properties": { "clickhouse": { + "description": "Executed ClickHouse query", "type": "string" }, "columns": { + "description": "Returned columns", "items": {}, "type": "array" }, + "error": { + "description": "Query error. Returned only if 'explain' is true. Throws an error otherwise.", + "type": "string" + }, "explain": { + "description": "Query explanation output", "items": { "type": "string" }, "type": "array" }, "hogql": { + "description": "Generated HogQL query", "type": "string" }, "modifiers": { - "$ref": "#/definitions/HogQLQueryModifiers" + "$ref": "#/definitions/HogQLQueryModifiers", + "description": "Modifiers used when performing the query" }, "query": { + "description": "Input query string", "type": "string" }, "results": { + "description": "Query results", "items": {}, "type": "array" }, "timings": { + "description": "Measured timings for different parts of the query generation process", "items": { "$ref": "#/definitions/QueryTiming" }, "type": "array" }, "types": { + "description": "Types of returned columns", "items": {}, "type": "array" } diff --git a/frontend/src/queries/schema.ts b/frontend/src/queries/schema.ts index 6e8fab0a961c9..fdba8908a3a71 100644 --- a/frontend/src/queries/schema.ts +++ b/frontend/src/queries/schema.ts @@ -139,14 +139,25 @@ export interface HogQLQueryModifiers { } export interface HogQLQueryResponse { + /** Input query string */ query?: string + /** Generated HogQL query */ hogql?: string + /** Executed ClickHouse query */ clickhouse?: string + /** Query results */ results?: any[] - types?: any[] + /** Query error. Returned only if 'explain' is true. Throws an error otherwise. */ + error?: string + /** Returned columns */ columns?: any[] + /** Types of returned columns */ + types?: any[] + /** Measured timings for different parts of the query generation process */ timings?: QueryTiming[] + /** Query explanation output */ explain?: string[] + /** Modifiers used when performing the query */ modifiers?: HogQLQueryModifiers } diff --git a/frontend/src/scenes/debug/HogQLDebug.tsx b/frontend/src/scenes/debug/HogQLDebug.tsx index cf5b662a3f8f1..21cc7b0b13104 100644 --- a/frontend/src/scenes/debug/HogQLDebug.tsx +++ b/frontend/src/scenes/debug/HogQLDebug.tsx @@ -91,6 +91,14 @@ export function HogQLDebug({ query, setQuery, queryKey }: HogQLDebugProps): JSX. ) : ( <> + {response?.error ? ( + <> +

Error Running Query!

+ + {response.error} + + + ) : null} {response?.hogql ? ( <>

Executed HogQL

diff --git a/posthog/hogql/query.py b/posthog/hogql/query.py index 697305d0ae964..c7e8c82713b15 100644 --- a/posthog/hogql/query.py +++ b/posthog/hogql/query.py @@ -1,6 +1,7 @@ from typing import Dict, Optional, Union, cast from posthog.clickhouse.client.connection import Workload +from posthog.errors import ExposedCHQueryError from posthog.hogql import ast from posthog.hogql.constants import HogQLGlobalSettings from posthog.hogql.errors import HogQLException @@ -143,16 +144,27 @@ def execute_hogql_query( timings=timings_dict, ) - results, types = sync_execute( - clickhouse_sql, - clickhouse_context.values, - with_column_types=True, - workload=workload, - team_id=team.pk, - readonly=True, - ) + error = None + try: + results, types = sync_execute( + clickhouse_sql, + clickhouse_context.values, + with_column_types=True, + workload=workload, + team_id=team.pk, + readonly=True, + ) + except Exception as e: + if explain: + results, types = None, None + if isinstance(e, ExposedCHQueryError) or isinstance(e, HogQLException): + error = str(e) + else: + error = "Unknown error" + else: + raise e - if explain: + if explain and error is None: # If the query errored, explain will fail as well. with timings.measure("explain"): explain_results = sync_execute( f"EXPLAIN {clickhouse_sql}", @@ -170,6 +182,7 @@ def execute_hogql_query( query=query, hogql=hogql, clickhouse=clickhouse_sql, + error=error, timings=timings.to_list(), results=results, columns=print_columns, diff --git a/posthog/hogql_queries/hogql_query_runner.py b/posthog/hogql_queries/hogql_query_runner.py index 576419fdff967..4326a2ba7dbee 100644 --- a/posthog/hogql_queries/hogql_query_runner.py +++ b/posthog/hogql_queries/hogql_query_runner.py @@ -65,6 +65,7 @@ def calculate(self) -> HogQLQueryResponse: workload=Workload.ONLINE, timings=self.timings, in_export_context=self.in_export_context, + explain=bool(self.query.explain), ) def _is_stale(self, cached_result_package): diff --git a/posthog/schema.py b/posthog/schema.py index 77bbd8cb01cb3..3b86559f8fc78 100644 --- a/posthog/schema.py +++ b/posthog/schema.py @@ -830,15 +830,22 @@ class HogQLQueryResponse(BaseModel): model_config = ConfigDict( extra="forbid", ) - clickhouse: Optional[str] = None - columns: Optional[List] = None - explain: Optional[List[str]] = None - hogql: Optional[str] = None - modifiers: Optional[HogQLQueryModifiers] = None - query: Optional[str] = None - results: Optional[List] = None - timings: Optional[List[QueryTiming]] = None - types: Optional[List] = None + clickhouse: Optional[str] = Field(default=None, description="Executed ClickHouse query") + columns: Optional[List] = Field(default=None, description="Returned columns") + error: Optional[str] = Field( + default=None, description="Query error. Returned only if 'explain' is true. Throws an error otherwise." + ) + explain: Optional[List[str]] = Field(default=None, description="Query explanation output") + hogql: Optional[str] = Field(default=None, description="Generated HogQL query") + modifiers: Optional[HogQLQueryModifiers] = Field( + default=None, description="Modifiers used when performing the query" + ) + query: Optional[str] = Field(default=None, description="Input query string") + results: Optional[List] = Field(default=None, description="Query results") + timings: Optional[List[QueryTiming]] = Field( + default=None, description="Measured timings for different parts of the query generation process" + ) + types: Optional[List] = Field(default=None, description="Types of returned columns") class LifecycleFilter(BaseModel):