Skip to content

Commit 6a3fad6

Browse files
sampenningtonclaude
andcommitted
chore(insights): address review feedback
- add extractAll/extractAllGroupsHorizontal/extractAllGroupsVertical/extractGroups to coerced functions - exclude StringArrayType (not StringJSONType) from nullable string coercion - parameterize the nullable-arg test across all array-returning functions, assert ifNull is emitted - add a non-nullable-arg case asserting no spurious ifNull Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 0c017e3 commit 6a3fad6

2 files changed

Lines changed: 45 additions & 8 deletions

File tree

posthog/hogql/printer/clickhouse.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,11 @@ def team_id_guard_for_table(table_type: ast.TableOrSelectType, context: HogQLCon
8181
"splitByWhitespace",
8282
"splitByNonAlpha",
8383
"alphaTokens",
84+
"extractAll",
8485
"extractAllGroups",
86+
"extractAllGroupsHorizontal",
87+
"extractAllGroupsVertical",
88+
"extractGroups",
8589
"ngrams",
8690
"tokens",
8791
}
@@ -172,14 +176,21 @@ def _render_function_call(self, node: ast.Call, func_meta) -> str:
172176
else:
173177
args.append(f"ifNull(toString({self.visit(arg)}), '')")
174178
elif node.name in _ARRAY_RETURNING_STRING_FUNCTIONS:
175-
# These return Array(...). A Nullable string argument would make the
176-
# result Nullable(Array(...)), which ClickHouse rejects, so coerce
177-
# nullable string args to a non-nullable empty string.
179+
# A Nullable string arg would make the result Nullable(Array(...)),
180+
# which ClickHouse rejects, so coerce nullable string args to a
181+
# non-nullable empty string. Coercion is position-agnostic on
182+
# purpose — a nullable separator would be just as illegal.
178183
args = []
179184
for arg in node_args:
180185
visited = self.visit(arg)
181186
arg_type = arg.type.resolve_constant_type(self.context) if arg.type is not None else None
182-
if isinstance(arg_type, ast.StringType) and arg_type.nullable:
187+
# StringArrayType is a StringType subclass but resolves to an
188+
# Array — coercing it to a bare '' would be invalid, so exclude it.
189+
if (
190+
isinstance(arg_type, ast.StringType)
191+
and not isinstance(arg_type, ast.StringArrayType)
192+
and arg_type.nullable
193+
):
183194
args.append(f"ifNull({visited}, '')")
184195
else:
185196
args.append(visited)

posthog/hogql/printer/test/test_printer.py

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4813,11 +4813,37 @@ def test_can_call_parametric_function(self):
48134813
)
48144814
assert query_response.results == [(6,)]
48154815

4816-
def test_split_function_on_nullable_property(self):
4817-
# splitByChar on a nullable property would produce an illegal Nullable(Array)
4818-
query = parse_select("SELECT splitByChar('@', properties.email) FROM events")
4816+
@parameterized.expand(
4817+
[
4818+
("splitByChar", "splitByChar('@', properties.email)"),
4819+
("splitByString", "splitByString('@@', properties.email)"),
4820+
("splitByRegexp", "splitByRegexp('[^a-z]+', properties.email)"),
4821+
("splitByWhitespace", "splitByWhitespace(properties.email)"),
4822+
("splitByNonAlpha", "splitByNonAlpha(properties.email)"),
4823+
("alphaTokens", "alphaTokens(properties.email)"),
4824+
("ngrams", "ngrams(properties.email, 3)"),
4825+
("tokens", "tokens(properties.email)"),
4826+
("extractAll", "extractAll(properties.email, '[a-z]+')"),
4827+
("extractAllGroups", "extractAllGroups(properties.email, '([a-z]+)')"),
4828+
("extractAllGroupsHorizontal", "extractAllGroupsHorizontal(properties.email, '([a-z]+)')"),
4829+
("extractAllGroupsVertical", "extractAllGroupsVertical(properties.email, '([a-z]+)')"),
4830+
("extractGroups", "extractGroups(properties.email, '([a-z]+)')"),
4831+
]
4832+
)
4833+
def test_array_returning_string_function_on_nullable_property(self, _name, func_call):
4834+
# A nullable string arg to an array-returning function would produce an
4835+
# illegal Nullable(Array(...)) — ClickHouse rejects it (exception_code=43).
4836+
query = parse_select(f"SELECT {func_call} FROM events")
4837+
query_response = execute_hogql_query(team=self.team, query=query)
4838+
assert query_response.clickhouse is not None
4839+
assert "ifNull(" in query_response.clickhouse
4840+
4841+
def test_array_returning_string_function_no_spurious_ifnull_on_constant(self):
4842+
# A non-nullable (constant) string arg must not be wrapped in ifNull.
4843+
query = parse_select("SELECT splitByChar('@', 'a@b') FROM events")
48194844
query_response = execute_hogql_query(team=self.team, query=query)
4820-
assert query_response.results is not None
4845+
assert query_response.clickhouse is not None
4846+
assert "ifNull(" not in query_response.clickhouse
48214847

48224848

48234849
class TestPostgresPrinter(BaseTest):

0 commit comments

Comments
 (0)