Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(hogql): query modifiers #17946

Merged
merged 10 commits into from
Oct 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions ee/clickhouse/queries/test/__snapshots__/test_lifecycle.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -501,7 +501,7 @@
AND event = '$pageview'
AND timestamp >= toDateTime(dateTrunc('day', toDateTime('2021-04-28 00:00:00', 'UTC'))) - INTERVAL 1 day
AND timestamp < toDateTime(dateTrunc('day', toDateTime('2021-05-05 23:59:59', 'UTC'))) + INTERVAL 1 day
AND (like(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person_props, 'email'), ''), 'null'), '^"|"$', ''), '%test.com'))
AND (like(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person_properties, 'email'), ''), 'null'), '^"|"$', ''), '%test.com'))
GROUP BY pdi.person_id)
GROUP BY start_of_period,
status)
Expand Down Expand Up @@ -576,7 +576,7 @@
AND event = '$pageview'
AND timestamp >= toDateTime(dateTrunc('day', toDateTime('2021-04-28 00:00:00', 'UTC'))) - INTERVAL 1 day
AND timestamp < toDateTime(dateTrunc('day', toDateTime('2021-05-05 23:59:59', 'UTC'))) + INTERVAL 1 day
AND (like(nullIf(nullIf(pmat_email, ''), 'null'), '%test.com'))
AND (like(nullIf(nullIf(mat_pp_email, ''), 'null'), '%test.com'))
GROUP BY pdi.person_id)
GROUP BY start_of_period,
status)
Expand Down
16 changes: 16 additions & 0 deletions frontend/src/queries/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1213,6 +1213,9 @@
"const": "HogQLQuery",
"type": "string"
},
"modifiers": {
"$ref": "#/definitions/HogQLQueryModifiers"
},
"query": {
"type": "string"
},
Expand All @@ -1228,6 +1231,16 @@
"required": ["kind", "query"],
"type": "object"
},
"HogQLQueryModifiers": {
"additionalProperties": false,
"description": "HogQL Query Options are automatically set per team. However, they can be overriden in the query.",
"properties": {
"personsOnEventsMode": {
"type": "string"
}
},
"type": "object"
},
"HogQLQueryResponse": {
"additionalProperties": false,
"properties": {
Expand All @@ -1241,6 +1254,9 @@
"hogql": {
"type": "string"
},
"modifiers": {
"$ref": "#/definitions/HogQLQueryModifiers"
},
"query": {
"type": "string"
},
Expand Down
7 changes: 7 additions & 0 deletions frontend/src/queries/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,11 @@ export interface DataNode extends Node {
response?: Record<string, any>
}

/** HogQL Query Options are automatically set per team. However, they can be overriden in the query. */
export interface HogQLQueryModifiers {
personsOnEventsMode?: string
}

export interface HogQLQueryResponse {
query?: string
hogql?: string
Expand All @@ -138,6 +143,7 @@ export interface HogQLQueryResponse {
types?: any[]
columns?: any[]
timings?: QueryTiming[]
modifiers?: HogQLQueryModifiers
}

/** Filters object that will be converted to a HogQL {filters} placeholder */
Expand All @@ -152,6 +158,7 @@ export interface HogQLQuery extends DataNode {
filters?: HogQLFilters
/** Constant values that can be referenced with the {placeholder} syntax in the query */
values?: Record<string, any>
modifiers?: HogQLQueryModifiers
response?: HogQLQueryResponse
}

Expand Down
21 changes: 21 additions & 0 deletions frontend/src/scenes/debug/HogQLDebug.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { dataNodeLogic, DataNodeLogicProps } from '~/queries/nodes/DataNode/data
import { ElapsedTime, Timings } from '~/queries/nodes/DataNode/ElapsedTime'
import { CodeSnippet, Language } from 'lib/components/CodeSnippet'
import { CodeEditor } from 'lib/components/CodeEditors'
import { LemonSelect } from 'lib/lemon-ui/LemonSelect'
import { LemonLabel } from 'lib/lemon-ui/LemonLabel'

interface HogQLDebugProps {
query: HogQLQuery
Expand All @@ -23,6 +25,25 @@ export function HogQLDebug({ query, setQuery }: HogQLDebugProps): JSX.Element {
<DateRange key="date-range" query={query} setQuery={setQuery} />
<EventPropertyFilters key="event-property" query={query} setQuery={setQuery} />
</div>
<div className="flex">
<LemonLabel>
POE:
<LemonSelect
options={[
{ value: 'disabled', label: 'Disabled' },
{ value: 'v1_enabled', label: 'V1 Enabled' },
{ value: 'v2_enabled', label: 'V2 Enabled' },
]}
onChange={(value) =>
setQuery({
...query,
modifiers: { ...query.modifiers, personsOnEventsMode: value },
} as HogQLQuery)
}
value={(query.modifiers ?? response?.modifiers)?.personsOnEventsMode}
/>
</LemonLabel>
</div>
{dataLoading ? (
<>
<h2>Running query...</h2>
Expand Down
4 changes: 3 additions & 1 deletion posthog/api/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from posthog.hogql.database.database import create_hogql_database, serialize_database
from posthog.hogql.errors import HogQLException
from posthog.hogql.metadata import get_hogql_metadata
from posthog.hogql.modifiers import create_default_modifiers_for_team
from posthog.hogql.query import execute_hogql_query

from posthog.hogql_queries.query_runner import get_query_runner
Expand Down Expand Up @@ -236,6 +237,7 @@ def process_query(
query=hogql_query.query,
team=team,
filters=hogql_query.filters,
modifiers=hogql_query.modifiers,
placeholders=values,
default_limit=default_limit,
)
Expand All @@ -245,7 +247,7 @@ def process_query(
metadata_response = get_hogql_metadata(query=metadata_query, team=team)
return _unwrap_pydantic_dict(metadata_response)
elif query_kind == "DatabaseSchemaQuery":
database = create_hogql_database(team.pk)
database = create_hogql_database(team.pk, modifiers=create_default_modifiers_for_team(team))
return serialize_database(database)
elif query_kind == "TimeToSeeDataSessionsQuery":
sessions_query_serializer = SessionsQuerySerializer(data=query_json)
Expand Down
18 changes: 9 additions & 9 deletions posthog/api/test/__snapshots__/test_insight.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
/* user_id:0 request:_snapshot_ */
SELECT groupArray(value)
FROM
(SELECT array(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person_props, 'fish'), ''), 'null'), '^"|"$', '')) AS value,
(SELECT array(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person_properties, 'fish'), ''), 'null'), '^"|"$', '')) AS value,
count(*) as count
FROM events e
INNER JOIN
Expand Down Expand Up @@ -79,7 +79,7 @@
if(step_0 = 1, timestamp, null) as latest_0,
if(event = 'user did things', 1, 0) as step_1,
if(step_1 = 1, timestamp, null) as latest_1,
array(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person_props, 'fish'), ''), 'null'), '^"|"$', '')) AS prop_basic,
array(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person_properties, 'fish'), ''), 'null'), '^"|"$', '')) AS prop_basic,
prop_basic as prop,
argMinIf(prop, timestamp, notEmpty(arrayFilter(x -> notEmpty(x), prop))) over (PARTITION by aggregation_target) as prop_vals
FROM events e
Expand Down Expand Up @@ -170,7 +170,7 @@
AND toTimeZone(timestamp, 'UTC') >= toDateTime('2012-01-08 00:00:00', 'UTC')
AND toTimeZone(timestamp, 'UTC') <= toDateTime('2012-01-15 23:59:59', 'UTC')
AND ((and(ifNull(less(toInt64OrNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(properties, 'int_value'), ''), 'null'), '^"|"$', '')), 10), 0), 1))
AND (like(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person_props, 'fish'), ''), 'null'), '^"|"$', ''), '%fish%')))
AND (like(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person_properties, 'fish'), ''), 'null'), '^"|"$', ''), '%fish%')))
AND (step_0 = 1
OR step_1 = 1) ))
WHERE step_0 = 1 ))
Expand Down Expand Up @@ -215,11 +215,11 @@
person.person_props as person_props ,
if(event = 'user signed up'
AND (and(ifNull(less(toInt64OrNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(properties, 'int_value'), ''), 'null'), '^"|"$', '')), 10), 0), 1)
AND like(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person_props, 'fish'), ''), 'null'), '^"|"$', ''), '%fish%')), 1, 0) as step_0,
AND like(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person_properties, 'fish'), ''), 'null'), '^"|"$', ''), '%fish%')), 1, 0) as step_0,
if(step_0 = 1, timestamp, null) as latest_0,
if(event = 'user did things'
AND (and(ifNull(less(toInt64OrNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(properties, 'int_value'), ''), 'null'), '^"|"$', '')), 10), 0), 1)
AND like(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person_props, 'fish'), ''), 'null'), '^"|"$', ''), '%fish%')), 1, 0) as step_1,
AND like(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person_properties, 'fish'), ''), 'null'), '^"|"$', ''), '%fish%')), 1, 0) as step_1,
if(step_1 = 1, timestamp, null) as latest_1
FROM events e
INNER JOIN
Expand Down Expand Up @@ -438,7 +438,7 @@
AND toTimeZone(timestamp, 'UTC') >= toDateTime(toStartOfDay(toDateTime('2012-01-08 00:00:00', 'UTC')), 'UTC')
AND toTimeZone(timestamp, 'UTC') <= toDateTime('2012-01-15 23:59:59', 'UTC')
AND ((and(ifNull(greater(toInt64OrNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(properties, 'int_value'), ''), 'null'), '^"|"$', '')), 10), 0), 1))
AND (like(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person_props, 'fish'), ''), 'null'), '^"|"$', ''), '%fish%')))
AND (like(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person_properties, 'fish'), ''), 'null'), '^"|"$', ''), '%fish%')))
GROUP BY date)
GROUP BY day_start
ORDER BY day_start)
Expand Down Expand Up @@ -506,7 +506,7 @@
AND toTimeZone(timestamp, 'UTC') >= toDateTime(toStartOfDay(toDateTime('2012-01-08 00:00:00', 'UTC')), 'UTC')
AND toTimeZone(timestamp, 'UTC') <= toDateTime('2012-01-15 23:59:59', 'UTC')
AND ((and(ifNull(greater(toInt64OrNull(nullIf(nullIf(events.mat_int_value, ''), 'null')), 10), 0), 1))
AND (like(nullIf(nullIf(pmat_fish, ''), 'null'), '%fish%')))
AND (like(nullIf(nullIf(mat_pp_fish, ''), 'null'), '%fish%')))
GROUP BY date)
GROUP BY day_start
ORDER BY day_start)
Expand Down Expand Up @@ -548,7 +548,7 @@
AND toTimeZone(timestamp, 'UTC') >= toDateTime(toStartOfDay(toDateTime('2012-01-08 00:00:00', 'UTC')), 'UTC')
AND toTimeZone(timestamp, 'UTC') <= toDateTime('2012-01-15 23:59:59', 'UTC')
AND (and(ifNull(less(toInt64OrNull(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(properties, 'int_value'), ''), 'null'), '^"|"$', '')), 10), 0), 1)
AND like(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person_props, 'fish'), ''), 'null'), '^"|"$', ''), '%fish%'))
AND like(replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(person_properties, 'fish'), ''), 'null'), '^"|"$', ''), '%fish%'))
GROUP BY date)
GROUP BY day_start
ORDER BY day_start)
Expand Down Expand Up @@ -590,7 +590,7 @@
AND toTimeZone(timestamp, 'UTC') >= toDateTime(toStartOfDay(toDateTime('2012-01-08 00:00:00', 'UTC')), 'UTC')
AND toTimeZone(timestamp, 'UTC') <= toDateTime('2012-01-15 23:59:59', 'UTC')
AND (and(ifNull(less(toInt64OrNull(nullIf(nullIf(events.mat_int_value, ''), 'null')), 10), 0), 1)
AND like(nullIf(nullIf(pmat_fish, ''), 'null'), '%fish%'))
AND like(nullIf(nullIf(mat_pp_fish, ''), 'null'), '%fish%'))
GROUP BY date)
GROUP BY day_start
ORDER BY day_start)
Expand Down
8 changes: 7 additions & 1 deletion posthog/hogql/ai.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from posthog.hogql.printer import print_ast
from .database.database import create_hogql_database, serialize_database
from posthog.utils import get_instance_region
from .query import create_default_modifiers_for_team

if TYPE_CHECKING:
from posthog.models import User, Team
Expand Down Expand Up @@ -52,7 +53,12 @@ class PromptUnclear(Exception):

def write_sql_from_prompt(prompt: str, *, current_query: Optional[str] = None, team: "Team", user: "User") -> str:
database = create_hogql_database(team.pk)
context = HogQLContext(team_id=team.pk, enable_select_queries=True, database=database)
context = HogQLContext(
team_id=team.pk,
enable_select_queries=True,
database=database,
modifiers=create_default_modifiers_for_team(team),
)
serialized_database = serialize_database(database)
schema_description = "\n\n".join(
(
Expand Down
7 changes: 3 additions & 4 deletions posthog/hogql/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
from typing import TYPE_CHECKING, Dict, List, Literal, Optional, Any

from posthog.hogql.timings import HogQLTimings
from posthog.utils import PersonOnEventsMode
from posthog.schema import HogQLNotice
from posthog.schema import HogQLNotice, HogQLQueryModifiers

if TYPE_CHECKING:
from posthog.hogql.database.database import Database
Expand All @@ -29,8 +28,6 @@ class HogQLContext:
values: Dict = field(default_factory=dict)
# Are we small part of a non-HogQL query? If so, use custom syntax for accessed person properties.
within_non_hogql_query: bool = False
# Do we need to join the persons table or not. Has effect if within_non_hogql_query = True
person_on_events_mode: PersonOnEventsMode = PersonOnEventsMode.V1_ENABLED
# Enable full SELECT queries and subqueries in ClickHouse
enable_select_queries: bool = False
# Do we apply a limit of MAX_SELECT_RETURNED_ROWS=10000 to the topmost select query?
Expand All @@ -44,6 +41,8 @@ class HogQLContext:
notices: List["HogQLNotice"] = field(default_factory=list)
# Timings in seconds for different parts of the HogQL query
timings: HogQLTimings = field(default_factory=HogQLTimings)
# Modifications requested by the HogQL client
modifiers: HogQLQueryModifiers = field(default_factory=HogQLQueryModifiers)

def add_value(self, value: Any) -> str:
key = f"hogql_val_{len(self.values)}"
Expand Down
7 changes: 5 additions & 2 deletions posthog/hogql/database/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
from posthog.hogql.errors import HogQLException
from posthog.models.group_type_mapping import GroupTypeMapping
from posthog.models.team.team import WeekStartDay
from posthog.schema import HogQLQueryModifiers
from posthog.utils import PersonOnEventsMode


Expand Down Expand Up @@ -108,13 +109,15 @@ def add_warehouse_tables(self, **field_definitions: Any):
setattr(self, f_name, f_def)


def create_hogql_database(team_id: int) -> Database:
def create_hogql_database(team_id: int, modifiers: Optional[HogQLQueryModifiers] = None) -> Database:
from posthog.models import Team
from posthog.hogql.query import create_default_modifiers_for_team
from posthog.warehouse.models import DataWarehouseTable, DataWarehouseSavedQuery, DataWarehouseViewLink

team = Team.objects.get(pk=team_id)
modifiers = create_default_modifiers_for_team(team, modifiers)
database = Database(timezone=team.timezone, week_start_day=team.week_start_day)
if team.person_on_events_mode != PersonOnEventsMode.DISABLED:
if modifiers.personsOnEventsMode != PersonOnEventsMode.DISABLED:
# TODO: split PoE v1 and v2 once SQL Expression fields are supported #15180
database.events.fields["person"] = FieldTraverser(chain=["poe"])
database.events.fields["person_id"] = StringDatabaseField(name="person_id")
Expand Down
8 changes: 7 additions & 1 deletion posthog/hogql/database/test/test_s3_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from posthog.hogql.database.database import create_hogql_database
from posthog.hogql.parser import parse_select
from posthog.hogql.printer import print_ast
from posthog.hogql.query import create_default_modifiers_for_team
from posthog.test.base import BaseTest
from posthog.hogql.database.test.tables import create_aapl_stock_s3_table
from posthog.hogql.errors import HogQLException
Expand All @@ -12,7 +13,12 @@ def _init_database(self):
self.database = create_hogql_database(self.team.pk)
self.database.aapl_stock = create_aapl_stock_s3_table()
self.database.aapl_stock_2 = create_aapl_stock_s3_table(name="aapl_stock_2")
self.context = HogQLContext(team_id=self.team.pk, enable_select_queries=True, database=self.database)
self.context = HogQLContext(
team_id=self.team.pk,
enable_select_queries=True,
database=self.database,
modifiers=create_default_modifiers_for_team(self.team),
)

def _select(self, query: str, dialect: str = "clickhouse") -> str:
return print_ast(parse_select(query), self.context, dialect=dialect)
Expand Down
8 changes: 7 additions & 1 deletion posthog/hogql/database/test/test_saved_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from posthog.hogql.database.database import create_hogql_database
from posthog.hogql.parser import parse_select
from posthog.hogql.printer import print_ast
from posthog.hogql.query import create_default_modifiers_for_team
from posthog.test.base import BaseTest
from posthog.hogql.database.test.tables import (
create_aapl_stock_table_view,
Expand All @@ -20,7 +21,12 @@ def _init_database(self):
self.database.aapl_stock = create_aapl_stock_s3_table()
self.database.aapl_stock_nested_view = create_nested_aapl_stock_view()
self.database.aapl_stock_self = create_aapl_stock_table_self_referencing()
self.context = HogQLContext(team_id=self.team.pk, enable_select_queries=True, database=self.database)
self.context = HogQLContext(
team_id=self.team.pk,
enable_select_queries=True,
database=self.database,
modifiers=create_default_modifiers_for_team(self.team),
)

def _select(self, query: str, dialect: str = "clickhouse") -> str:
return print_ast(parse_select(query), self.context, dialect=dialect)
Expand Down
8 changes: 7 additions & 1 deletion posthog/hogql/database/test/test_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from posthog.hogql.database.database import create_hogql_database
from posthog.hogql.parser import parse_select
from posthog.hogql.printer import print_ast
from posthog.hogql.query import create_default_modifiers_for_team
from posthog.test.base import BaseTest
from posthog.hogql.database.test.tables import (
create_aapl_stock_table_view,
Expand All @@ -20,7 +21,12 @@ def _init_database(self):
self.database.aapl_stock = create_aapl_stock_s3_table()
self.database.aapl_stock_nested_view = create_nested_aapl_stock_view()
self.database.aapl_stock_self = create_aapl_stock_table_self_referencing()
self.context = HogQLContext(team_id=self.team.pk, enable_select_queries=True, database=self.database)
self.context = HogQLContext(
team_id=self.team.pk,
enable_select_queries=True,
database=self.database,
modifiers=create_default_modifiers_for_team(self.team),
)

def _select(self, query: str, dialect: str = "clickhouse") -> str:
return print_ast(parse_select(query), self.context, dialect=dialect)
Expand Down
Loading
Loading