Skip to content

ComposeCompiler has no duplicate-parameter-bind guard: a second bind of the same value shifts every later ordinal and 273 tests pass #3211

Description

@erikdarlingdata

The gap

ComposeCompiler builds every composed-panel query by appending SQL text while allocating positional parameters through ParamList, and the correspondence between the two is maintained by hand at each site. Nothing checks it. A site that binds the same value twice gets a fresh ordinal for the second bind, the SQL it writes is valid, the query runs, and every ordinal allocated after it shifts by one.

That is not hypothetical shape-spotting. It is the obvious wrong way to do the thing #3202 did, and I confirmed by mutation that it survives the suite.

What was removed, and what stayed green

In #3202's ComposeCompiler.CompileAnnotation, a server-local annotation source needs the panel's server array in two places: the outer f.server_name = ANY($3) predicate and the offset-join subquery that scopes the DISTINCT ON. The shipped code reuses the already-allocated $3. The tempting alternative is a second p.AddTextArray(context.Servers!), which compiles and reads correctly.

I applied that mutation and progressively removed the assertions guarding it:

assertions left in place duplicate bind detected?
everything as shipped yes — by Assert.Contains("AND server_name = ANY($3)", ...)
ordinal blinded to ANY($ — count assert kept yes — by Assert.Equal(3, scoped.Parameters.Count)
ordinal blinded and count assert removed no. Total: 273, Failed: 0

The third row is the finding. With two assertions in one new test removed, a duplicate parameter bind in the compose compiler is caught by nothing in 273 tests — and that count includes the whole of DarlingComposeTests, which is the suite that exists to hold this compiler's safety invariants.

Why DarlingComposeTests' own ordinal assertions do not see it

They are per-statement, and they assert the ordinals of the statement they were written for.

CompileAnnotations_EmitsSchemaQualifiedCollect_WindowAndServerBound_Capped does assert Assert.Equal(3, one.Parameters.Count) — but it compiles the deadlocks source, which is UTC-framed and therefore never takes the offset join, so the mutation cannot reach it. CompileAnnotations_EachSource_SelectsItsCatalogTimeAndLabelColumns does cover default_trace_events, but asserts only the ts and label expressions, not any parameter count. So the one test that counts parameters looks at a statement the bug cannot occur in, and the one test that looks at the affected statement does not count parameters.

That is the general shape of the problem, and it is why fixing it by adding another per-site assertion would not close it: an ordinal assertion on one statement is evidence about that statement. It says nothing about the next site someone adds, and the realistic regression is exactly a new site — a new annotation source, a new measure route, a new filter form — written by someone who binds a value they already had.

The instrument that would close it

ComposeCompiled already carries both halves of the invariant: Sql and Parameters. So the check is available without new plumbing, and it is a property of every compiled statement rather than of any one of them. Roughly:

  • Every $n appearing in Sql satisfies 1 <= n <= Parameters.Count.
  • Every ordinal in 1..Parameters.Count appears at least once in Sql — an allocated-but-unreferenced parameter is the signature of a duplicate bind, since the duplicate is allocated and the SQL still cites the original.
  • Applied across a corpus of compiled statements: every measure route, every annotation source, the server-scoped and fleet-wide variants of each, and the filter and variable forms.

The second bullet is the one that catches this specific mutation, and it is worth stating why it is the right discriminator rather than "count the $ns and compare": a duplicate bind leaves the ordinal count HIGHER than the highest ordinal used, so a max-based check passes while a coverage-based check fails. It fails toward the worse label.

Two cautions for whoever writes it. First, it must be proven to find the statements — a corpus sweep that compiles nothing reports a clean bill of health, so assert a floor on how many statements it examined, the way ServerLocalReadFrameDisciplineTests and CreationTimeClockFrameDisciplineTests both do. Second, red-proof it by the mutation above rather than by inspection: the whole reason this issue exists is that a guard which looked like it covered the compiler did not.

Provenance

Found while red-proofing #3202 (fix/3198-trace-event-time-frame), by crippling two guards at once rather than one at a time. Single-mutation testing would have missed it — with either assertion present the mutation is caught, so each looks individually sufficient and neither is revealed as the only thing holding the property. Relevant files:

  • Darling/PerformanceMonitor.Darling.Service/Compose/ComposeCompiler.cs — ParamList, CompileAnnotation, Compile
  • Darling/Darling.Tests/DarlingComposeTests.cs — the existing per-statement ordinal assertions
  • Darling/Darling.Tests/ServerLocalReadFrameDisciplineTests.cs — CompiledAnnotationSql_ScopesTheOffsetJoin_ToThePanelsOwnServerArray, the two assertions in question

Not claimed

No shipped compose statement has a duplicate bind today; I did not find a live defect. This is a missing check, and the argument for it is that the class is reachable, silent, and currently unguarded — not that it has already bitten.

Activity

  1. erikdarlingdata commented on Sep 9, 2026

    @erikdarlingdata
    OwnerAuthor

    Claude posting for Erik Darling

    Fixed by #3226, merged to dev as c9f04f3fe049. Closing by hand — closing keywords resolve against main.

    ComposeParameterCoverageTests now carries a per-plan parameter census, placeholder coverage checked in both directions, and a source scan over the compiler's 15 ParamList sites. The corpus is derived from the catalog — 578 statements binding three or more parameters — and reach is asserted as membership (every measure and annotation source must contribute) rather than as a total, so a corpus that shrank could not pass by arithmetic.

    The instrument this issue proposed does not catch the mutation this issue is named for, and that was measured rather than argued. I wrote that a duplicate bind leaves Parameters.Count higher than the highest ordinal used, "so a max check passes exactly where a coverage check fails." For the case that matters — a duplicate bind whose placeholder IS cited — coverage and max both read 0. Neither sees it. The per-plan census is what reds.

    So the reasoning in the body was clean and wrong, and the coverage-versus-max framing it rests on is not the discriminator. Worth keeping because it is the second time today a proposed instrument was refuted by building it: the guard-width derivation on #3189 was the first.

    How the defect was found in the first place is the part worth carrying, and it is why this could not have been caught by reading: on #3202 a duplicate bind was caught by nothing in 273 tests once two guards were removed together — the scoping pin's ordinal substring and its parameter-count assert. Either alone still red. DarlingComposeTests' own ordinal assertions never saw it, because the annotation sources they check do not take the join in question. A defect that requires crippling two instruments to expose is invisible to any single-mutation battery.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions