Skip to content

fix: allow grouping gauge and sum metric charts by materialized columns - #3191

Open
jordan-simonovski wants to merge 10 commits into
mainfrom
jordansimonovski/metric-materialized-dotted-columns
Open

jordan-simonovski wants to merge 10 commits into
mainfrom
jordansimonovski/metric-materialized-dotted-columns

Conversation

@jordan-simonovski

Copy link
Copy Markdown
Contributor

Summary

Gauge and sum metric charts failed with Code: 47 Unknown expression identifier when grouped by a MATERIALIZED or ALIAS column, such as a deployment.environment.name column pulled out of ResourceAttributes. They now work.

  • Charts can group by and select computed columns on the metric tables. Filtering on them already worked, because the filter runs against the base table.
  • Tables using the default OTel schema have no computed columns, so their SQL is unchanged.
  • A computed column named after one of the CTE's own aliases (AttributesHash, LastValue, Rate, Sum) is skipped, so adding one can't break charts that work today.
  • Histogram charts weren't affected: they evaluate the group-by against the base table.
Implementation detail

The gauge and sum paths build a Source CTE with SELECT *, which leaves out MATERIALIZED and ALIAS columns. A Bucketed CTE then picks a fixed list of columns, and the chart's group-by runs against Bucketed, so a computed column was never there to find. Both CTEs now pick up the table's computed columns from DESCRIBE. If that lookup fails, the chart renders the same SQL as before.

Tested with integration tests against ClickHouse that add a dotted MATERIALIZED column to the gauge and sum tables, then filter and group by it. Both fail on main with the same error and pass here. A unit test covers passing the columns through and skipping alias names.

The gauge and sum CTEs select * (which skips MATERIALIZED and ALIAS
columns) and then project a fixed column list, so an outer group-by on a
computed column failed with Unknown expression identifier. Carry those
columns through both CTEs.
@vercel

vercel Bot commented Sep 23, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated
hyperdx-oss Ready Ready Preview Sep 23, 2026 2:03pm UTC
hyperdx-storybook Ready Ready Preview Sep 23, 2026 2:03pm UTC

Request Review

@changeset-bot

changeset-bot Bot commented Sep 23, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 902a0a9

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

This PR includes changesets to release 4 packages
Name Type
@hyperdx/common-utils Patch
@hyperdx/api Patch
@hyperdx/app Patch
@hyperdx/otel-collector 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

@github-actions github-actions Bot added the review/tier-2 Low risk — AI review + quick human skim label Sep 23, 2026
@github-actions

github-actions Bot commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

🔴 Tier 4 — Critical

Touches authentication, tenancy data models, the public API or shipped database config — or substantially changes the query rendering engine, background tasks, the OTel pipeline, image build, or release CI.

Why this tier:

  • Query rendering engine substantially modified — 203 lines (bar: 150). Every chart, search, and alert query flows through this code:
    • packages/common-utils/src/core/renderChartConfig.ts

Review process: Deep review from a domain expert. Synchronous walkthrough may be required.
SLA: Schedule synchronous review within 2 business days.

Stats
  • Production files changed: 1
  • Production lines changed: 203 (+ 387 in test files, excluded from tier calculation)
  • Branch: jordansimonovski/metric-materialized-dotted-columns
  • Author: jordan-simonovski

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

@github-actions

github-actions Bot commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

Deep Review

Scope: Gauge/sum metric chart rendering — getComputedMetricColumns threads MATERIALIZED/ALIAS columns through the Source/Bucketed CTEs so charts can group by and select computed columns. Default OTel schemas (no computed columns) render byte-identical SQL.

No critical issues found. No P0/P1 ship-blockers surfaced. The prior-review P1 threads all appear resolved in the current code: any()-collapse of grouped values is fixed (grouped computed columns now join the bucket GROUP BY raw instead of being reduced by any()), the substring/whole-identifier match is fixed (service no longer matches serviceName), and qualified/quoted/dotted group-by refs (Bucketed.region, region.1, "region") are handled, each with unit coverage. Column names interpolated via UNSAFE_RAW_SQL are SqlString.escapeId-escaped or hardcoded OTel literals, so no SQL-injection path exists. metadata.getColumns is cached (getOrFetch), so the added lookup is at most one DESCRIBE per table, not per render.

🟡 P2 — recommended

  • packages/common-utils/src/core/renderChartConfig.ts:1919 — Every MATERIALIZED/ALIAS column is threaded through the CTEs even when unreferenced, so custom tables with many or expensive ALIAS columns rely on ClickHouse pruning unused any() projections; pruning is analyzer/version-dependent and differs for ALIAS (re-evaluated) vs MATERIALIZED (stored).
    • Fix: Restrict the carried columns to those referenced by the chart's select/groupBy (reuse the existing referenced set) so cost is independent of pruning behavior.
    • performance, previous-comments
  • packages/common-utils/src/core/renderChartConfig.ts:1857GAUGE_BUCKETED_COLUMNS/SUM_BUCKETED_COLUMNS must stay hand-synced with the gauge project() calls, the sum inner project() calls, and the sum outer literal SELECT list; drift silently drops a column from GROUP BY or projects it twice with no error.
    • Fix: Drive the project() invocation lists from the same arrays (map over the constant) or add a unit test asserting the array equals the emitted column set.
    • maintainability
  • packages/common-utils/src/core/renderChartConfig.ts:2209 — The Sum branch's computed-column string assembly (two computedColumns.select insertions plus Sum-specific project()/groupBy for MetricName, AggregationTemporality, IsMonotonic) has no unit coverage; all seven new unit tests use gaugeConfiguration, so a Sum-only rendering regression escapes if the ClickHouse integration suite is skipped.
    • Fix: Add a sum-config unit test mirroring the gauge "carries computed columns" / "groups buckets by grouped-by computed columns" assertions.
    • testing
  • packages/common-utils/src/core/renderChartConfig.ts:1922 — The catch fallback that degrades to the original CTE shape when getColumns throws is never exercised; only the empty-result path is covered by the default mock.
    • Fix: Add a unit test mocking getColumns to reject and assert the rendered SQL omits all computed-column fragments.
    • testing
  • packages/api/src/clickhouse/__tests__/renderChartConfig.int.test.ts:900 — The "ranks increase groups by the column" test asserts only that group labels appear (arrayContaining + objectContaining) and never validates the increase() numeric results, so a grouping that yields correct labels but wrong aggregation passes.
    • Fix: Assert exact per-group increase values via toEqual on a sorted [label, value] array, matching the sibling "varies within a series" test.
    • testing
🔵 P3 nitpicks (5)
  • packages/common-utils/src/core/renderChartConfig.ts:1960 — Two project bindings in one function with opposite escaping semantics (local const project emits escapeId-escaped identifiers for aggregate; the returned project emits raw unescaped OTel names via UNSAFE_RAW_SQL).
    • Fix: Rename to distinguish intent, e.g. projectEscaped vs projectFixedOtel.
  • packages/common-utils/src/core/renderChartConfig.ts:1967 — The returned project interpolates its argument raw and is safe only because all call sites pass hardcoded OTel literals; nothing enforces that invariant.
    • Fix: Escape unconditionally (the fixed names are escape-safe) so the raw-interpolation hatch cannot be misused by a future caller.
  • packages/common-utils/src/core/renderChartConfig.ts:1932 — The group-by identifier-extraction regex plus dotted-slice flatMap (three quote grammars, doubled-quote/backslash unescaping, sub-slice emission) is correctness-sensitive but hard to reason about inline.
    • Fix: Extract into a named helper (e.g. extractReferencedIdentifiers) with focused unit tests.
  • packages/common-utils/src/core/renderChartConfig.ts:2225 — The sum outer Bucketed CTE re-lists all 16 base columns as bare identifiers, duplicating the inner subquery's project() list; a column added to one list but forgotten here is aggregated then dropped.
    • Fix: Generate both lists from SUM_BUCKETED_COLUMNS.
  • packages/common-utils/src/core/renderChartConfig.ts:2165 — The "already carries" de-dup path is unit-tested only for gauge ServiceName; the Sum-only fixed names (MetricName, AggregationTemporality, IsMonotonic) colliding with a computed column of the same name are unverified (low real-world likelihood, but the defensive branch and the resulting duplicate outer-SELECT column are untested).
    • Fix: Add a sum-config case grouping by a computed column named after a Sum-only fixed column.

Reviewers (10): correctness, adversarial, security, performance, testing, maintainability, project-standards, kieran-typescript, previous-comments, learnings.

Testing gaps:

  • Sum metric branch has no unit-level coverage for computed-column assembly; it depends entirely on the ClickHouse integration suite running.
  • The getColumns-throws fallback branch is uncovered.
  • Several integration assertions (filters and groups on the column, ranks increase groups) validate group-by labels but not aggregated metric values, so a correct-labels/wrong-values regression could pass.
  • No test exercises a computed column whose name contains a backtick or SQL metacharacter to confirm escapeId output is both injection-safe and accepted by ClickHouse.

Coverage note: correctness, adversarial, previous-comments, TypeScript, and learnings reviewers were still completing at synthesis time; the correctness and previous-comments conclusions above were verified directly against the diff, the surrounding CTE code, and the full prior-comment thread.

No finding blocks merge automatically. A maintainer will expect P0/P1 findings in code this PR changes to be fixed; P2/P3 are your call -- fix or reply. Never fix findings about surrounding code here; reply instead. Do not widen the PR. How to respond

Comment thread packages/common-utils/src/core/renderChartConfig.ts Outdated
databaseName: from.databaseName,
tableName: metricTables[MetricsDataType.Gauge],
connectionId: chartConfig.connection,
reservedNames: ['AttributesHash', 'LastValue', timeBucketCol],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 minorreservedNames is a hand-maintained copy of the CTE projection and covers only 3 of ~17 aliases

Derive the reserved set from the projection (or from the filtered column list) rather than restating it. The gauge list is ['AttributesHash','LastValue',timeBucketCol] but the same CTE also defines ScopeAttributes, ResourceAttributes, Attributes, ResourceSchemaUrl, ScopeName, ScopeVersion, ScopeDroppedAttrCount, ScopeSchemaUrl, ServiceName, MetricDescription, MetricUnit, StartTimeUnix, Flags (lines 2008-2020); the sum list at line 2095 omits those plus MetricName, AggregationTemporality, IsMonotonic. A table that declares one of them computed — e.g. ServiceName String MATERIALIZED ResourceAttributes['service.name'], the pattern ClickHouse's own logs schemas use — emits any(ServiceName) AS ServiceName twice in Bucketed and projects the name twice in the sum outer select (line 2164), so the CTE result has a duplicate column and the invariant the doc comment claims ("names the CTE already defines are skipped") does not hold.

Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond

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

github-actions Bot commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

PR Review

If you are a coding agent acting for the author, read this first.
Nothing below blocks merge automatically; a maintainer decides. What they will expect:

  • 🔴 critical or 🟠 major in code this PR adds or changes: fix before asking for review.
  • 🔵 minor in code this PR changes: your call. Fix if small, otherwise reply.
  • Anything about surrounding code, or asking you to widen the change (hoist a helper,
    dedupe with another file, fix other call sites): never fix here, whatever the
    severity. Reply with one sentence; if it is critical, say so plainly so a human sees it.

One commit per review round. After two rounds, stop and ask a maintainer to review
scope rather than addressing more automated findings. Full rule: AGENTS.md.

4 finding(s): 🔴 0 critical · 🟠 1 major · 🔵 3 minor

4 posted as inline comment(s) on the changed lines.


Severity is the reviewer's own estimate and is used for ordering, not filtering. No finding blocks merge automatically; a maintainer decides.

@github-actions

github-actions Bot commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

E2E Test Results

All tests passed • 382 passed • 1 skipped • 1285s

Status Count
✅ Passed 382
❌ Failed 0
⚠️ Flaky 2
⏭️ Skipped 1

Tests ran across 4 shards in parallel.

View full report →

Fix the int test type error, cover increase + group-by on a computed
column, and log when listing computed columns fails.
Comment thread packages/common-utils/src/core/renderChartConfig.ts
@greptile-apps

greptile-apps Bot commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

The PR appears safe to merge.

Summary

Gauge and sum metric charts can now select and group by MATERIALIZED or ALIAS columns from their metric tables. The renderer finds those computed columns and carries them through its intermediate queries, while keeping existing OTel chart queries unchanged when no computed columns exist.

  • Computed columns now pass through gauge and sum chart CTEs.
  • Grouped computed columns stay separate when their values change.
  • Unit and ClickHouse tests cover dotted MATERIALIZED and ALIAS columns.
Diagram
sequenceDiagram
    participant Chart as Chart request
    participant Renderer as renderChartConfig
    participant Metadata as Metadata
    participant CH as ClickHouse
    Chart->>Renderer: Gauge or sum chart with groupBy
    Renderer->>Metadata: Load metric table columns
    Metadata-->>Renderer: MATERIALIZED and ALIAS columns
    Renderer->>Renderer: Find computed columns used by groupBy
    Renderer->>CH: Run Source CTE with computed columns
    Renderer->>CH: Run Bucketed CTE with grouped columns preserved
    CH-->>Renderer: Grouped gauge or sum rows
    Renderer-->>Chart: Chart query results
Loading

Reviews (10) · Last reviewed commit: "fix: group by OTel-named computed column..."

Comment thread packages/common-utils/src/core/renderChartConfig.ts Outdated
Comment thread packages/common-utils/src/core/renderChartConfig.ts Outdated
karl-power
karl-power previously approved these changes Sep 23, 2026
Comment thread packages/common-utils/src/core/renderChartConfig.ts Outdated
Grouped-by MATERIALIZED/ALIAS columns now feed AttributesHash, so the
per-bucket any() can't merge rows with different values. They go in via
tuple() because cityHash64 returns NULL for a NULL argument.
Comment thread packages/common-utils/src/core/renderChartConfig.ts Outdated
@greptile-apps

This comment has been minimized.

A substring match hashed `service` when grouping by `serviceName`,
splitting series the chart didn't ask to split.
Comment thread packages/common-utils/src/core/renderChartConfig.ts Outdated
Comment thread packages/common-utils/src/core/renderChartConfig.ts Outdated
Comment thread packages/common-utils/src/core/renderChartConfig.ts Outdated
Comment thread packages/common-utils/src/core/renderChartConfig.ts Outdated
…lumns

Match Bucketed.region and region.1 against region, and tokenise without a
dynamic RegExp so lint stays under the warning cap.
Comment thread packages/common-utils/src/core/renderChartConfig.ts Outdated
AttributesHash also partitions the sum rate window, so folding group-by
columns into it reset the window on a label change and dropped the step.
`host.name` no longer also counts as a reference to host and name.
Comment thread packages/common-utils/src/core/renderChartConfig.ts Outdated
Comment thread packages/common-utils/src/core/renderChartConfig.ts
groupByText
.replace(/'(?:[^'\\]|\\.)*'/g, "''")
.match(/`[^`]+`|"[^"]+"|[\p{L}\p{N}_$.]+/gu) ?? []
).flatMap(token => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 minor — Slicing unqualified dotted tokens makes a JSON sub-column path false-match a same-named computed column

Only treat the whole token as a reference (plus an explicit Bucketed.-qualified prefix if needed), or resolve the leading segment against the table's columns first. With a JSON ResourceAttributes column and a MATERIALIZED region, a group-by of ResourceAttributes.`region (the form mergePath builds, packages/app/src/utils.ts:1160-1178) adds region to the bucket GROUP BY, splitting each series-bucket into several LastValue rows and silently changing gauge avg/sum/quantile output.

Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond

Comment thread packages/common-utils/src/core/renderChartConfig.ts Outdated
typeof groupBy === 'string'
? groupBy
: (groupBy ?? []).map(g => g.valueExpression).join(',');
const referenced = new Set(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 minor — Hand-rolled column-reference tokenizer duplicates extractColumnReferencesFromKey

Reuse extractColumnReferencesFromKey (packages/common-utils/src/clickhouse/index.ts:342 — same input shape, already used by core/metadata.ts:141, app/src/source.ts:336, app/src/utils/materializedViews.ts:434, DBRowTable.tsx:1418), extending it if backtick-quoted dotted names need handling; otherwise add the doc comment the repo conventions require naming the twin and why both must exist. Same for escape on line 1915 — the file already emits identifiers via chSql's Identifier binding (line 1221-1224) and core/metadata.ts exports quoteIdentifierIfNeeded.

Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond

Unescape doubled quotes and backslashes in quoted group-by names. Skip
computed columns the Bucketed CTE already projects with any(), which
otherwise made ClickHouse resolve GROUP BY to that alias.
Comment thread packages/common-utils/src/core/renderChartConfig.ts Outdated
Flags,
AggregationTemporality,
IsMonotonic
IsMonotonic${{ UNSAFE_RAW_SQL: computedColumns.select }}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 major — Sum Bucketed outer SELECT re-projects computed columns it already lists literally

The sum path appends computedColumns.select (the unfiltered columnNames) to the outer Bucketed projection, but that projection already lists all 16 SUM_BUCKETED_COLUMNS by name. On a custom sum table where one of those names is computed — e.g. ServiceName String MATERIALIZED ResourceAttributes['service.name'], exactly the shape this PR targets — the inner subquery emits any(ServiceName) AS ServiceName once (correctly, since bucketed filters it out), and the outer CTE then projects ServiceName twice, making the reference ambiguous/duplicated for the outer query that reads Bucketed. The gauge path is immune only because its select string is used solely in the SELECT * CTE. Return a fourth field built from bucketed (the already-filtered list) and use it here instead of select, and add a sum unit test mirroring 'does not re-project a computed column Bucketed already carries' — the new unit tests only exercise the gauge path, so nothing covers the sum CTE's third insertion point.

Nothing blocks merge automatically, but a maintainer will expect this fixed if it is a real defect in code this PR changes. If it is about surrounding code, reply and say so instead of patching. Do not widen the PR. How to respond

// alias in GROUP BY.
const bucketed = columnNames.filter(c => !bucketedColumns.includes(c));
return {
select: columnNames.map(c => `, ${escape(c)}`).join(''),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 major — Every ALIAS column on the metric table is now evaluated by every gauge/sum query

select/aggregate carry all MATERIALIZED and ALIAS columns, not just the ones the chart references, so each ALIAS expression is computed for every row in range and then any()-aggregated per bucket even for charts that never mention it. One badly-behaved ALIAS on the table (e.g. ALIAS toFloat64(Attributes['pct']), which throws on a non-numeric attribute, or an Array(String) items column like ResourceAttributeItems in docker/clickhouse/local/init-db-e2e.sh:115) then breaks or slows all gauge and sum charts, not just the one using it — a regression for tables that render fine today. The referenced set is already computed here; restrict the carry-through to columns the chart actually references (group-by plus orderBy/having, which restChartConfig forwards to the outer query).

Nothing blocks merge automatically, but a maintainer will expect this fixed if it is a real defect in code this PR changes. If it is about surrounding code, reply and say so instead of patching. Do not widen the PR. How to respond

}

// OTel columns each Bucketed CTE already projects with any().
const GAUGE_BUCKETED_COLUMNS = [

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 minorGAUGE_BUCKETED_COLUMNS/SUM_BUCKETED_COLUMNS restate the CTE projections as a second source of truth

These two constants must stay in exact sync with the literal any(...) lists 200-400 lines below (renderChartConfig.ts:2080-2092 and :2251-2266) with nothing tying them together; the sum finding above is what that drift already costs. Derive the list from a single array that both the constant and the generated any(...) projection are built from, so adding or removing a column in the CTE can't silently leave the carry-through logic wrong.

Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond

typeof groupBy === 'string'
? groupBy
: (groupBy ?? []).map(g => g.valueExpression).join(',');
const referenced = new Set(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 minor — Hand-rolled identifier tokenizer/unquoter duplicates existing helpers

Extracting column references from an expression already exists as extractColumnReferencesFromKey (packages/common-utils/src/clickhouse/index.ts:342, which handles map access and JSON paths), and stripping identifier quoting exists as unquoteIdentifier (packages/common-utils/src/core/metadata.ts:120). The repo's DRY rule is marked REQUIRED. Either build referenced on those (parameterizing the quoted-dotted-name behaviour you need) or, if the twin genuinely has to coexist, doc-comment why and name the existing symbol, per the conventions' "two genuinely must coexist" row.

Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond

A grouped computed column named like a fixed Bucketed column (ServiceName,
MetricName, ...) now replaces that column's any() projection and joins
the GROUP BY, so differing values stay apart.
@github-actions github-actions Bot added review/tier-4 Critical — deep review + domain expert sign-off and removed review/tier-2 Low risk — AI review + quick human skim labels Sep 23, 2026
Flags,
AggregationTemporality,
IsMonotonic
IsMonotonic${{ UNSAFE_RAW_SQL: computedColumns.select }}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 major — Sum Bucketed wrapper projects an OTel-named computed column twice

computedColumns.select is every computed column, but this outer wrapper already lists the 16 OTel columns by name (lines 2225–2239). If the sum table declares any of them MATERIALIZED/ALIAS — e.g. the common ServiceName MATERIALIZED ResourceAttributes['service.name'] — the CTE emits ServiceName, … , \ServiceName`, so ClickHouse rejects/ambiguates the reference from the outer query. The inner subquery already re-exposes those under their own names via project(), so only extrabelongs here: return a second string (e.g.selectExtra: extra.map(c => ", " + escape(c)).join("")) and use it at line 2240, keeping selectfor theSourceCTE at line 2209. Note the gauge equivalent is covered by the unit test atpackages/common-utils/src/tests/renderChartConfig.test.ts:380, but there is no sum-path unit test at all — add one with ServiceName` MATERIALIZED against the sum config.

Nothing blocks merge automatically, but a maintainer will expect this fixed if it is a real defect in code this PR changes. If it is about surrounding code, reply and say so instead of patching. Do not widen the PR. How to respond

}

// OTel columns each Bucketed CTE already projects with any().
const GAUGE_BUCKETED_COLUMNS = [

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 minorGAUGE_BUCKETED_COLUMNS/SUM_BUCKETED_COLUMNS are a hand-synced copy of the CTE projection lists

These arrays must stay identical to the literal computedColumns.project('…') lists at lines 2082–2094 and 2253–2268 and the wrapper list at 2225–2239; drift silently produces either a duplicate projection (the bug above) or a missing column. Drive the projections from the constants instead — e.g. GAUGE_BUCKETED_COLUMNS.map(c => computedColumns.project(c)) joined with , — so there is one source of truth.

Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond

const project = (c: string) =>
grouped.has(c) ? escape(c) : `any(${escape(c)}) AS ${escape(c)}`;
return {
select: columnNames.map(c => `, ${escape(c)}`).join(''),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 minor — Every computed column is carried through both CTEs even when the chart references none

select/aggregate are built from all of columnNames, so a metrics table with N MATERIALIZED/ALIAS columns adds N projections to Source and N any() aggregates to the bucket GROUP BY on every gauge/sum query, including charts that reference none of them (ALIAS columns are evaluated per row at query time). It also makes all metric charts fail after a computed column is dropped, since MetadataCache (packages/common-utils/src/core/metadata.ts:224) never evicts, so the stale name stays in the SQL until the process restarts. Filter columnNames down to the ones actually referenced — the referenced set is already computed here; extend it with chartConfig.orderBy — and fall back to nothing when the chart references none.

Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond

typeof groupBy === 'string'
? groupBy
: (groupBy ?? []).map(g => g.valueExpression).join(',');
const referenced = new Set(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 minor — Identifier extraction, unquoting and quoting are re-implemented instead of reusing existing helpers

Per the REQUIRED DRY rule, three existing implementations of these operations already exist: extractColumnReferencesFromKey (packages/common-utils/src/clickhouse/index.ts:342) pulls column references out of a comma-separated expression list, unquoteIdentifier (packages/common-utils/src/core/metadata.ts:120) — already imported at line 13 of this file — strips one level of `/" quoting, and quoteIdentifierIfNeeded/quoteJsonPathSegment (packages/common-utils/src/core/metadata.ts:148-162) backtick-quote a name. Reuse them, or if the doubled-quote/dotted-name handling genuinely requires a twin, add the doc comment naming the existing symbol and why it does not fit.

Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond

This branch was successfully deployed

2 active deployments
Preview – hyperdx-oss 902a0a9c Deployed Sep 23, 2026 by vercel[bot]
Preview – hyperdx-storybook 902a0a9c Deployed Sep 23, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review/tier-4 Critical — deep review + domain expert sign-off

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants