Skip to content

fix: unknown lucene field falls through in search (re-land #2422 + #2446) - #2498

Closed
karl-power wants to merge 2 commits into
mainfrom
karl/lucene-alias-v2
Closed

fix: unknown lucene field falls through in search (re-land #2422 + #2446)#2498
karl-power wants to merge 2 commits into
mainfrom
karl/lucene-alias-v2

Conversation

@karl-power

@karl-power karl-power commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Summary

Re-lands #2422 (the HDX-4367 unknown-field fix) together with #2446 (a ClickHouse parametric-aggregate parser fix). They must ship together: #2422 was reverted (#2447) precisely because it triggered the bug #2446 fixes.

Problem

  • An unresolved Lucene field was emitted verbatim as a raw SQL identifier, so ClickHouse rejected the query with Unknown identifier — killing the histogram and the row table at once (they share the parsed WHERE).
  • fix: unknown lucene field falls through in search #2422 fixed that but added extractSelectAliases, which feeds string-form selects to chSqlToAliasMap. That helper throws on ClickHouse parametric aggregates — the double-paren func(params)(args) form (e.g. groupUniqArray(20)(col)) — which value-autocomplete (getKeyValues) emits on every fetch.

What changed

  • queryParser.ts — gate the unresolved-field fall-through on a known-alias set (selectAliases): known alias → bare identifier (ClickHouse resolves SELECT aliases in WHERE); genuinely unknown → no-match predicate (1 = 0) instead of raw SQL. Plus a null-safety fix for the materialized-columns lookup.
  • renderChartConfig.ts — new extractSelectAliases collects SELECT-list + expression-WITH aliases (subquery CTEs excluded) and threads them through every Lucene call site (WHERE, filters, per-aggregate conditions, value expressions, HAVING). Memoized per chart config.
  • core/utils.ts + clickhouse/index.ts — new replaceParametricAggregates neutralizes the double-paren form before node-sql-parser runs (mirrors replaceJsonExpressions), then restores it. Only the genuine func(params)(args) form matches.

Behavior

Lucene field Before After
Real column (ServiceName:foo) resolves resolves (unchanged)
SELECT alias (Content:foo, Body AS Content) raw identifier (worked by luck) resolves explicitly
Unknown / typo (myTypo:foo) raw identifier → ClickHouse error (1 = 0) → no rows
Autocomplete on parametric-agg select chSqlToAliasMap throws + console spam parses cleanly

Tests

  • All in packages/common-utils (no app/api changes). 1355 unit tests pass, make ci-lint clean, E2E green.
  • New coverage: alias resolution across all Lucene call sites; (1 = 0) for unknown fields; chSqlToAliasMap parametric-aggregate cases incl. the getKeyValues repro; replaceParametricAggregates unit tests.
  • Interop regression guard: a string select with a parametric aggregate (quantile(0.9)(value) AS p90) resolves its alias in a Lucene WHERE — fails without either fix, pinning the exact interaction that forced the revert.

How to test on preview (/search)

  1. Search an existing field, a SELECT alias, and a typo/non-existent field — the unknown field returns no rows instead of erroring out chart + table.
  2. Open value autocomplete — confirm no Error parsing alias map console spam.
  3. Confirm saved-search alerts referencing a select alias still fire.

References

Closes HDX-4367 · re-lands #2422 + #2446 · original revert #2447

)

Re-lands the HDX-4367 unknown-field fix (#2422) together with the
parametric-aggregate parser fix (#2446) that the original revert (#2447)
was needed for, so the fall-through fix ships safely as one change.

- queryParser.ts: gate the unresolved-field fall-through on a known-alias
  set (selectAliases); a genuinely unknown field now renders the no-match
  predicate (1 = 0) instead of a raw SQL identifier ClickHouse rejects.
- renderChartConfig.ts: extractSelectAliases collects SELECT-list and
  expression-WITH aliases and threads them through every Lucene call site
  (WHERE, filters, per-aggregate conditions, value expressions, HAVING).
- core/utils.ts + clickhouse/index.ts: replaceParametricAggregates lets
  chSqlToAliasMap parse the ClickHouse double-paren func(params)(args) form
  (eg. groupUniqArray(20)(col)) instead of throwing — the gap that surfaced
  on every value-autocomplete fetch once extractSelectAliases started
  feeding string-form selects to chSqlToAliasMap.

Refs HDX-4367, #2422, #2446, #2447.
@changeset-bot

changeset-bot Bot commented Jun 22, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 82efa23

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@hyperdx/common-utils Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercel Bot commented Jun 22, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
hyperdx-oss Ready Ready Preview, Comment Jun 22, 2026 1:33pm
hyperdx-storybook Ready Ready Preview, Comment Jun 22, 2026 1:33pm

Request Review

@github-actions github-actions Bot added the review/tier-3 Standard — full human review required label Jun 22, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🟡 Tier 3 — Standard

Introduces new logic, modifies core functionality, or touches areas with non-trivial risk.

Why this tier:

  • Diff size: 264 production lines changed (Tier 2 max: < 250)

Review process: Full human review — logic, architecture, edge cases.
SLA: First-pass feedback within 1 business day.

Stats
  • Production files changed: 4
  • Production lines changed: 264 (+ 627 in test files, excluded from tier calculation)
  • Branch: karl/lucene-alias-v2
  • Author: karl-power

To override this classification, remove the review/tier-3 label and apply a different review/tier-* label. Manual overrides are preserved on subsequent pushes.

@greptile-apps

greptile-apps Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR re-lands two previously reverted fixes as a single atomic change: unknown Lucene fields no longer fall through as raw SQL identifiers that ClickHouse rejects, and chSqlToAliasMap no longer throws on ClickHouse parametric aggregate functions. The two fixes are tightly coupled — the alias-collection path (extractSelectAliases → chSqlToAliasMap) requires the parametric-aggregate parser fix before it can safely back the alias-gating fix.

  • queryParser.ts: The old "it might be an alias" fall-through in getColumnForField is replaced with an explicit selectAliases set check; unknown fields now return found: false, rendering as (1 = 0) rather than raw SQL.
  • core/utils.ts + clickhouse/index.ts: replaceParametricAggregates uses a paren-balancing scanner to tokenize and restore the double-paren func(params)(args) form before node-sql-parser runs, mirroring the existing replaceJsonExpressions pattern.
  • renderChartConfig.ts: extractSelectAliases collects aliases from array-form selects, string-form selects (via chSqlToAliasMap), and expression-form WITH clauses; the result is memoized per config via a WeakMap and threaded into all five Lucene call sites.

Confidence Score: 5/5

Safe to merge — the change is a well-scoped fix with comprehensive test coverage across all Lucene call sites and an explicit interop regression guard.

All production usages of CustomSchemaSQLSerializerV2 go through renderChartConfig → renderWhereExpressionStr, which now always computes and passes selectAliases. The replaceParametricAggregates scanner correctly handles quotes, nested parens, and unbalanced input via a null-returning fallback. The WeakMap cache addresses the prior review comment about redundant parsing. The combined regression test pins the exact interaction that forced the original revert.

No files require special attention.

Important Files Changed

Filename Overview
packages/common-utils/src/core/utils.ts Adds replaceParametricAggregates, a paren-balancing scanner that correctly handles ClickHouse double-paren func(params)(args) form; quote regions are skipped, whitespace between groups is tolerated, and unbalanced input safely falls through
packages/common-utils/src/clickhouse/index.ts Wires replaceParametricAggregates into chSqlToAliasMap ahead of replaceJsonExpressions; restoration now uses a merged Map so both token types are correctly expanded
packages/common-utils/src/queryParser.ts Replaces the unconditional fall-through with an explicit selectAliases set check; unknown fields now return found: false; adds a null-safety fix for getMaterializedColumnsLookupTable returning null/undefined
packages/common-utils/src/core/renderChartConfig.ts Adds extractSelectAliases with WeakMap memoization and threads selectAliases through all five Lucene call sites; string-form selects parsed via chSqlToAliasMap, expression-form WITH clauses included, subquery CTEs excluded
packages/common-utils/src/tests/renderChartConfig.test.ts Adds 14 new test cases covering all Lucene call sites, the interop regression guard, subquery CTE exclusion, and the no-match predicate for genuinely unknown fields

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["renderChartConfig(chartConfig)"] --> B["extractSelectAliases(chartConfig) WeakMap cached"]
    B --> C{chartConfig.select type?}
    C -->|array| D["Collect col.alias for each entry"]
    C -->|string| E["chSqlToAliasMap"]
    E --> E1["replaceParametricAggregates"]
    E1 --> E2["replaceJsonExpressions"]
    E2 --> E3["node-sql-parser extract aliases"]
    E3 --> E4["Restore both token sets"]
    D --> F["selectAliases Set"]
    E4 --> F
    B --> G["chartConfig.with isSubquery false only"]
    G --> F
    F --> H["renderWhereExpressionStr with selectAliases"]
    H --> I["CustomSchemaSQLSerializerV2"]
    I --> J["getColumnForField"]
    J --> K{field in real columns?}
    K -->|yes| L["found: true real column"]
    K -->|no| M{field in selectAliases?}
    M -->|yes| N["found: true bare identifier"]
    M -->|no| O["found: false renders as 1=0"]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A["renderChartConfig(chartConfig)"] --> B["extractSelectAliases(chartConfig) WeakMap cached"]
    B --> C{chartConfig.select type?}
    C -->|array| D["Collect col.alias for each entry"]
    C -->|string| E["chSqlToAliasMap"]
    E --> E1["replaceParametricAggregates"]
    E1 --> E2["replaceJsonExpressions"]
    E2 --> E3["node-sql-parser extract aliases"]
    E3 --> E4["Restore both token sets"]
    D --> F["selectAliases Set"]
    E4 --> F
    B --> G["chartConfig.with isSubquery false only"]
    G --> F
    F --> H["renderWhereExpressionStr with selectAliases"]
    H --> I["CustomSchemaSQLSerializerV2"]
    I --> J["getColumnForField"]
    J --> K{field in real columns?}
    K -->|yes| L["found: true real column"]
    K -->|no| M{field in selectAliases?}
    M -->|yes| N["found: true bare identifier"]
    M -->|no| O["found: false renders as 1=0"]
Loading

Reviews (2): Last reviewed commit: "refactor: address review feedback (memoi..." | Re-trigger Greptile

Comment thread packages/common-utils/src/core/renderChartConfig.ts Outdated
Comment thread packages/common-utils/src/queryParser.ts
@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

E2E Test Results

All tests passed • 202 passed • 3 skipped • 1299s

Status Count
✅ Passed 202
❌ Failed 0
⚠️ Flaky 2
⏭️ Skipped 3

Tests ran across 4 shards in parallel.

View full report →

@karl-power
karl-power marked this pull request as draft June 22, 2026 13:25
…hangeset)

- Memoize extractSelectAliases per chart config via a WeakMap so a single
  renderChartConfig no longer reparses string-form select lists up to three
  times across renderSelectList/renderWhere/renderHaving (Greptile P2).
- Consolidate the two changesets (#2422 + #2446 re-land) into one.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review/tier-3 Standard — full human review required

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant