Skip to content

Fix #18: emit parse_path in function-call form for lambda params#21

Open
piotrszul wants to merge 1 commit into
masterfrom
fix/18_splitpath-function-form
Open

Fix #18: emit parse_path in function-call form for lambda params#21
piotrszul wants to merge 1 commit into
masterfrom
fix/18_splitpath-function-form

Conversation

@piotrszul

Copy link
Copy Markdown
Collaborator

Problem

The default (struct) backend generated DuckDB SQL that fails to bind on DuckDB 1.4.x for any forEach/forEachOrNull select containing a getReferenceKey() column (e.g. EncounterFlat's participant/location):

Binder Error: Referenced column "el" not found in FROM clause!

The same SQL ran fine on DuckDB 1.5.x. Reported in #18.

Root cause

getReferenceKey(Type) expands to reference.where(_splitPath(-2) = 'Type')._splitPath(-1). The _splitPath step inside the where() predicate compiled to el.parse_path('/')[...] — a method call with the lambda parameter as the receiver. When that predicate sits in a list_filter nested inside the forEach's list_transform, DuckDB 1.4.x's binder fails to resolve the lambda parameter.

The issue originally hypothesised parameter shadowing (inner el shadowing outer el). Investigation against DuckDB 1.4.1 showed that is not the cause:

  • Renaming the inner parameter to a unique name (el_1) does not fix it.
  • Reusing the same name el together with the function-call form works fine.
  • The trigger is specifically a lambda parameter used as a method-call receiver (el.parse_path(...)) inside a list_filter predicate nested in another lambda. Nested list_transform with the same construct binds fine; plain (non-receiver) uses of the parameter bind fine.

Fix

Emit the equivalent function-call form parse_path(el, '/')[...] instead of the method-call form el.parse_path('/')[...] when _splitPath operates on a lambda parameter. This binds correctly on both 1.4.x and 1.5.x. The non-lambda _splitPath case stays method-chained so flattenSql can still join it onto the preceding navigation segment.

One-case, general change in src/ddb-sql-builder.js.

Verification

  • New e2e test (tests/e2e.test.js) reproduces the issue's minimal case (forEachOrNull: participant + individual.getReferenceKey(Practitioner)) and executes the generated SQL against the bundled DuckDB 1.4.1 — RED before the fix (Binder Error), GREEN after.
  • Full suite: 192 pass / 0 fail (baseline 191; +1 new test).
  • End-to-end: built the issue's MinimalRepro.json via the CLI and ran the generated .sql on DuckDB 1.4.1 → id,practitioner_id / enc1,prac1.

Notes / out of scope

  • This is a DuckDB-1.4.x binder limitation; the generated SQL was already valid on 1.5.x. The fix makes flatquack emit SQL that binds on both.
  • The experimental staged backend was already unaffected (it uses UNNEST stages rather than nested list-lambdas), so no change there.
  • Branch is based directly on origin/master; no other fixes folded in.

🤖 Generated with Claude Code

The struct backend generated DuckDB SQL that failed to bind on DuckDB
1.4.x for any forEach/forEachOrNull select containing a getReferenceKey()
column, e.g.:

  Binder Error: Referenced column "el" not found in FROM clause!

getReferenceKey() expands to a where() predicate whose _splitPath step
compiles to `el.parse_path('/')[...]` — a method call with the lambda
parameter as the receiver. When that predicate sits in a list_filter
nested inside the forEach's list_transform, DuckDB 1.4.x's binder fails
to resolve the lambda parameter. It is not a name-shadowing problem
(renaming the inner parameter does not help, and reusing the same name
with the function-call form works fine); the trigger is specifically a
lambda parameter used as a *method-call receiver* inside a nested
list_filter predicate.

Emitting the equivalent function-call form `parse_path(el, '/')[...]`
binds correctly on both 1.4.x and 1.5.x. The non-lambda _splitPath case
stays method-chained so flattenSql can join it onto the preceding
navigation segment.

Verification:
- New e2e test reproduces the issue's minimal case (forEachOrNull
  participant + getReferenceKey(Practitioner)) and runs the generated
  SQL against the bundled DuckDB 1.4.1 — RED before, GREEN after.
- Full suite: 192 pass / 0 fail (was 191; +1 new test).
- End-to-end: built the issue's MinimalRepro.json via the CLI and ran
  the generated SQL on DuckDB 1.4.1 -> id,practitioner_id / enc1,prac1.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@piotrszul piotrszul force-pushed the fix/18_splitpath-function-form branch from c078d18 to 7865271 Compare June 10, 2026 10:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant