Skip to content

refactor(db): one renderer for the container-path sentence - #1092

Merged
cevheri merged 2 commits into
libredb:mainfrom
sloemo01:refactor/container-path-renderer
Sep 27, 2026
Merged

cevheri merged 2 commits into
libredb:mainfrom
sloemo01:refactor/container-path-renderer

Conversation

@sloemo01

@sloemo01 sloemo01 commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

What

Fifteen provider files built their own container path is [...] refusal. Eleven derived the level list locally, four carried a private shapeList(), and two of those four had already drifted: SQL Server's had lost the guard for a declaration naming no level, so an empty one printed A SQL Server container path is , received [...].

assertContainerPathShape in object-kinds.ts is now the only producer, the way assertObjectPathShape has been for object paths since #1023.

Closes #1065.

The fifteen were not one shape

Reading all of them showed policies hiding in the duplication, so the descriptor carries each one instead of collapsing them.

Field Why it exists
shapes Most engines accept exactly the declared depth. Couchbase, Trino, DuckDB and SQL Server accept every prefix up to it, because naming only the outer levels is a real address there (a bucket with no scope, a catalog with no schema).
shapeNames Trino and PostgreSQL spell the shape from level.id, not the label. On their own declarations the two are the same word, so only a varied declaration shows it, and Trino has a test that varies one to prove it. Every other engine prints the label, which is the engine's word for a person reading a refusal.
emptyShapes An engine declaring no container level spells that in its own words rather than printing an empty join that reads as a formatting bug.

The object-path renderer took no such fields because its nine copies agreed on all of these. These three do not.

PostgreSQL's readers look the schema level up rather than reading by position, so a declaration that names none is refused by containerSchema itself, next to the reads it protects. An earlier revision tried a requireLevel field on the shared type; it is gone, because a rule only one engine's readers need does not belong in a type fourteen others set nothing on.

Tests

tests/unit/db/container-path-renderer.test.ts is the mechanical half of the acceptance criterion the issue names, in the same spirit as object-surface-conformance.test.ts for the object family. It reads the provider tree rather than exercising a provider, because the claim is about where the sentence is built: a behavioural test can only show that one engine's message is right, and the defect was that fifteen engines each built their own.

The detector strips both comment forms first and then matches the phrase inside any string literal, in any quote style. A backtick-only matcher missed a quoted throw; that is pinned by a mutation check now. The fifteen engine files are listed by name rather than behind a floor, because a floor cannot see the case it exists to notice: an engine that stops reaching the renderer takes the count down, and >= 14 still passes for fifteen minus one.

Each descriptor field is pinned by a provider test, and a mutation reddens exactly its own engine:

  • ignoring shapeNames fails only the Trino test that varies the declaration
  • collapsing prefixes to exact fails 37 tests across Trino and SQL Server
  • removing the schema refusal from containerSchema fails only the new PostgreSQL test

The PostgreSQL test is new, because nothing covered a declaration whose depth matches but which names no schema level. A first draft of this change let a prefixes engine accept the empty path for an empty declaration, which Trino's and DuckDB's tests caught immediately: every prefix of an empty list is a shape the engine never declared.

object-route.ts cites line numbers in two of the shifted files and a test pins every citation, so those five are updated to the lines their anchors now sit on. Worth knowing for anyone moving code in these files: running the formatter after fixing them moved the lines a second time, and the citations had to be re-measured.

Also removed

Four containerShapes() helpers, Couchbase's shapeList(), the dead empty arms on the Trino and DuckDB shapeList()s (their only caller builds the shapes from a declaration, so the empty case is unreachable there and the shared renderer owns it), two provider-local declaredLevels() copies, and the imports they orphaned. Eight files were importing object-kinds twice; the descriptors that sat between the imports moved below them, so Cassandra, ClickHouse, Druid and Trino now build theirs from their own PROVIDER/TYPE_ID.

Verification

format (idempotent), lint (0 errors), typecheck, knip, chart:check, readme:check, security:check all pass. Full bun run test under the pinned Bun 1.4.2: 648 of 649 files pass, and the one red file, tests/unit/launcher-utils.test.ts, is not in this diff. It passes standalone both before and after this branch, so it is a local-machine flake around the ambient node its floor check probes. All fifteen provider integration suites re-run green here.

@codecov

codecov Bot commented Sep 23, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@cevheri cevheri left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks. I checked the old conditions against the shared check on all fifteen engines and found no disagreement. Keeping Druid's containerSchema as its reader is the right call. One more round before I merge:

  • requireLevel is a PostgreSQL-only rule in the shared type, and the hoist dropped the undefined check in containerSchema, so an undefined schema segment now binds as $1. Please drop the field and give PostgreSQL a local reader like Druid's containerSegment (druid/objects.ts:371) that names the missing level and refuses undefined, and point the test at postgres-provider.test.ts:5056 at it.
  • The only-producer test misses a quoted-string throw, and >= 14 lets one engine leave. Please match the phrase in any string literal after stripping comments, pin the fifteen files, and drop the dead object-kinds.ts skip.
  • Leftovers: the unreachable empty arm and "shapes above" docblocks on shapeList() in trino and duckdb, the docblock at mssql.ts:1037, {@link containerShapes} at trino/objects.ts:553, and the rename in prose at sqlite.ts:1490.
  • Eight files import object-kinds twice, and seven declare the descriptor between imports. Moving it below them lets cassandra, clickhouse, druid and trino use their PROVIDER/TYPE_ID.

@sloemo01

Copy link
Copy Markdown
Contributor Author

Rebased onto main (d44250e0) and pushed as b132509a. The branch was dirty over one conflict, in src/lib/api/object-route.ts: both sides had renumbered the same docblock citations after their own file moves, and each side's numbers were right for its own tree and wrong for the merge.

I let the test that pins those citations settle it instead of picking a side. tests/unit/lib/api/object-route-edit.test.ts derives every number from its anchor line, so a wrong one fails with the exact numbers the merged tree needs. That is what the resolution writes, and the test passes on the result.

Nothing else changed in the rebase. 14 provider suites plus the citation and renderer unit files (2728 tests) pass under bun 1.4.2, and all checks are green on the new head (SonarCloud and Image Scan skip on forks as usual).

@cevheri

cevheri commented Sep 26, 2026 •

Copy link
Copy Markdown
Member

@sloemo01 , we wrote comment, same-time :)

please check it: #1092 (review)

@sloemo01

Copy link
Copy Markdown
Contributor Author

@sloemo01 , we wrote comment, same-time :)

please check it: #1092 (review)

Just saw this lol
There's still a minute gap if I'm not wrong.
Could you also tell me when this would be merged

@cevheri

cevheri commented Sep 27, 2026

Copy link
Copy Markdown
Member

Thanks. I checked the old conditions against the shared check on all fifteen engines and found no disagreement. Keeping Druid's containerSchema as its reader is the right call. One more round before I merge:

  • requireLevel is a PostgreSQL-only rule in the shared type, and the hoist dropped the undefined check in containerSchema, so an undefined schema segment now binds as $1. Please drop the field and give PostgreSQL a local reader like Druid's containerSegment (druid/objects.ts:371) that names the missing level and refuses undefined, and point the test at postgres-provider.test.ts:5056 at it.
  • The only-producer test misses a quoted-string throw, and >= 14 lets one engine leave. Please match the phrase in any string literal after stripping comments, pin the fifteen files, and drop the dead object-kinds.ts skip.
  • Leftovers: the unreachable empty arm and "shapes above" docblocks on shapeList() in trino and duckdb, the docblock at mssql.ts:1037, {@link containerShapes} at trino/objects.ts:553, and the rename in prose at sqlite.ts:1490.
  • Eight files import object-kinds twice, and seven declare the descriptor between imports. Moving it below them lets cassandra, clickhouse, druid and trino use their PROVIDER/TYPE_ID.

@sloemo01 :) One more round before I merge

@cevheri

cevheri commented Sep 27, 2026

Copy link
Copy Markdown
Member

Also: big news > we now support Apache Kafka, Prometheus, and MCP Server in the new 0.17.0 release. I want to share this great news with you. thanks for your contributions. your name is in here: https://lnkd.in/p/dZ2C4dgn

)

Fifteen provider files built their own `container path is [...]` refusal, eleven
deriving the level list locally and four carrying a private `shapeList()`, two of
which had already drifted. `assertContainerPathShape` now produces all of them,
the way `assertObjectPathShape` does for object paths since libredb#1023.

Reading the fifteen showed they were not one shape. Three policies were hiding in
them and the descriptor carries each one rather than collapsing them:

- `shapes`: most engines accept exactly the declared depth; Couchbase, Trino,
  DuckDB and SQL Server accept every prefix up to it, because naming only the
  outer levels is a real address there.
- `shapeNames`: Trino and PostgreSQL spell the shape from `level.id`, not the
  label, which only a varied declaration shows. Every other engine prints the
  label, which is the engine's own word for a person reading a refusal.
- `emptyShapes`: an engine declaring no container level says why in its own
  words rather than printing an empty join that reads as a formatting bug.

PostgreSQL's readers look the `schema` level up rather than reading by position,
so a declaration that names none is refused by `containerSchema` itself, next to
the reads it protects, and not by a field on the shared type that no other engine
would ever set.

`object-route.ts` cited line numbers in two of the shifted files, and the test
that pins every citation is updated to the lines its anchors now sit on.

Four `containerShapes()` helpers, two `declaredLevels()` copies and the imports
they orphaned are gone. `tests/unit/db/container-path-renderer.test.ts` is the
mechanical half of the acceptance criterion: it reads the provider tree and fails
if any provider builds the sentence itself, matches the phrase in any quote style
after comments are stripped, pins the fifteen engine files by name rather than
behind a floor, or keeps a private container shape helper. Each descriptor field
is pinned by a provider test, and ignoring one reddens exactly its own engine.
@sloemo01
sloemo01 force-pushed the refactor/container-path-renderer branch from b132509 to 6063e18 Compare September 27, 2026 01:24
@sloemo01

Copy link
Copy Markdown
Contributor Author

All four addressed in 6063e183, rebased onto main (bb31671e).

requireLevel is gone. You were right that it did not belong on the shared type: fourteen engines set nothing on it, and the hoist had dropped the undefined check the old containerSchema carried. The field is deleted, PostgreSQL's descriptor declares no level, and containerSchema refuses a declaration with no schema level itself, the way Druid's containerSegment does, naming the missing level and the path. The test at 5056 now asserts that sentence: A PostgreSQL path needs a "schema" container level and a segment for it; the declaration is [catalog] and the path is ["shop"].

The detector is tightened. It strips both comment forms first (including a docblock whose continuation lines carry no marker), then matches the phrase inside any string literal in any quote style, so the double-quoted throw the old backtick-only matcher read as clean is caught. I proved that by injecting one: the file goes red with the line in the message, and green again after restore. The fifteen files are pinned by name instead of >= 14. A floor cannot see its own case: an engine that leaves takes the count down, and 14 still passes for fifteen minus one. The object-kinds.ts skip is dropped too; it was dead, since that file is not under providers/ and never reached the loop.

The leftovers are cleaned. Trino's and DuckDB's shapeList() lost the empty arm and the "shapes above" docblocks; their only caller builds from a declaration, so the empty case is the shared renderer's emptyShapes and the arm was unreachable. Same for the docblock at mssql:1037, the {@link containerShapes} at trino/objects.ts:553 now points at the shapeNames note on ContainerPathShapeEngine, and the sqlite prose reads "THE STATEMENT SQLITE RECEIVED" again.

Imports and descriptor placement. Eight files were importing object-kinds twice; the second import merged into the first in each. The seven descriptors that sat between imports moved below them, and Cassandra, ClickHouse, Druid and Trino now build theirs from their own PROVIDER/TYPE_ID constants.

Verified on the rebased head under the pinned Bun 1.4.2: format (idempotent), lint 0 errors, typecheck, knip, security:check, readme:check, chart:check all clean; all fifteen provider integration suites green (2799 tests); the full suite runs 648 of 649 files, the one red file being launcher-utils.test.ts, which is not in this diff and passes standalone before and after this branch (it probes the ambient node for a floor check, and this machine does not have the CI's Node 24). Also re-derived the five object-route.ts citation numbers from the pinning test after the formatter settled.

The line scanner stripped `//` without knowing about strings, so a throw carrying a URL before the phrase read as clean.
The detector now walks the string and template nodes of the parsed file, the way the seam guards read a provider, so comments never reach it.

Also drops the blank line the descriptor move left inside seven import blocks, and a double space in Oracle's descriptor docblock.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] One shared renderer for the container-path sentence, not fifteen producers

2 participants