Skip to content

SHOW TABLES reports the client vocabulary, through one authority (BIDC-10a Part D) - #320

Merged
fupelaqu merged 3 commits into
mainfrom
feature/BIDC-10a
Sep 10, 2026
Merged

SHOW TABLES reports the client vocabulary, through one authority (BIDC-10a Part D)#320
fupelaqu merged 3 commits into
mainfrom
feature/BIDC-10a

Conversation

@fupelaqu

Copy link
Copy Markdown
Contributor

Part D of story BIDC-10a: SHOW TABLES reports the client-facing table-type vocabulary through a
single authority, instead of leaking elasticsql's internal TableType names.

The lead ruling behind it: fix the vocabulary at the source, not in each transport. Prompted by a
live CI regression on arrow PR #177, where unifying GetTableTypes and GetTables on the internal
vocabulary made the two agree with each other and stop agreeing with every client — the ADBC leg
failed with Expected 'TABLE' in ['REGULAR'].

What changes

surface before after
SHOW TABLES type, plain index REGULAR TABLE
SHOW TABLES type, materialized view MATERIALIZED_VIEW MATERIALIZED VIEW (spaced)
SHOW TABLE <t> header (REPL \st) Table: users [Regular] Table: users [TABLE]
VIEW · EXTERNAL · CHANGELOG · ENRICHMENT unchanged

🔴 A BI tool observes NO change from this. The JDBC and Flight TABLE_TYPE contracts are
unchanged: a plain index already reached a browser as TABLE through the driver's own mapping, and a
materialized view already reached it as VIEW. Story 20.3's frozen advertised type list is
respected, not reversed. What moves is the engine surface only.

Nothing stored changes. _meta.type still holds regular / materialized_view, still parsed by
TableType(...). No reindex, no migration. A test asserts the round trip for every type.

Design

TableType gains an abstract sqlName. Abstract, not a mapping function with a fallback case,
because a fallback is exactly how REGULAR shipped in the first place — a seventh table type now
fails to compile until someone decides its client-facing name. name (storage) and sqlName
(display) have disjoint consumers, so no abstraction is invented.

Why the materialized-view spelling is spaced, and it is not an appeal to another engine's
convention: every statement naming the object is spaced — CREATE MATERIALIZED VIEW,
SHOW MATERIALIZED VIEW, SHOW MATERIALIZED VIEW STATUS, SHOW CREATE MATERIALIZED VIEW,
DESCRIBE MATERIALIZED VIEW. An underscore would be the only place the product spells its own object
differently from the statement that creates it.

What the review round caught

An independent fresh-context reviewer found that the first implementation created the divergence
it set out to close: ResultRenderer renders the case object via toString, so it was invisible to a
grep for the expression being replaced, and it left SHOW TABLES saying TABLE while SHOW TABLE
said [Regular]35 lines apart in the same documentation walkthrough. Both projections now read
sqlName, and a test asserts they agree for every type.

It also found the new scaladoc asserting an invariant (name "is never a display value") that a third
consumer 80 lines below breaks. The engine's own refusal Cannot alter table <t> of type materialized_view legitimately names the engine's own type; that is now documented as the one
deliberate exception rather than silently contradicted.

Verification

  • sql/test 1044/1044 · core/test 956/956
  • + sql/compile, + core/compile, and explicit ++ 2.12.20 test compiles — green on 2.12.20 and 2.13.16
  • GatewayApiIntegrationSpec on real ES, all five clients: 6.8 rest, 6.8 jest, 7.17, 8.18, 9.0
  • headerCheck · scalafmtSbtCheck · scalafmtCheck · test:scalafmtCheck
  • Falsified, not assumed: reverting the projection reddens 3 of 4 core tests; restoring the underscore
    reddens exactly 3 of 9 sql tests; dropping a type from the expectation reddens 3 with a clue naming it.

The exhaustiveness gate walks the compiled package rather than a hand-written list. Its first
version silently returned empty — the spec shares a package with TableType, so test-classes shadowed
the main classes dir and 5 of 8 assertions passed vacuously. It now unions every root and fails on
emptiness inside the enumerator.

Downstream

⚠️ Two driver mappings are PERMANENT, not shims. arrow's and jdbc's contains("view")VIEW
are load-bearing: removing either makes every materialized view vanish from an object browser, because
getTableTypes advertises only TABLE and VIEW and getTables filters by exact match. Only their
contains("regular") branches become dead at the repin onto a core carrying this change. Each repo
pins its own collapse against both spellings.

A downstream pinning the literal strings REGULAR or MATERIALIZED_VIEW must be updated. TableType
is sealed, so the added abstract member is source- and binary-compatible for consumers.

Sibling branches, held for review, no PRs yet: feature/BIDC-10a in softclient4es-extensions (testkit
expectation), softclient4es-arrow and softclient4es-jdbc (the capability declaration), and
softclient4es-web (a published walkthrough showing the old value).

No issue is filed for this story, per the epic's issue-lifecycle rule, so this PR carries no closing
keyword.

🤖 Generated with Claude Code

…ugh one authority

Story BIDC-10a Part D (AC 10, AC 12, T7).

`SHOW TABLES` published elasticsql's INTERNAL table-type vocabulary: the `type` column
was `TableType.name.toUpperCase`, so a plain index came back as `REGULAR` - a value no
client consumes. The sidecar's ADBC leg failed on exactly that (`Expected 'TABLE' in
['REGULAR']`), and the same column is what a JDBC `getTables`, a Flight SQL `GET_TABLES`
and an ODBC object browser receive, because all three execute `SHOW TABLES` through the
gateway. Fixing it in each transport would mean a translation layer per client, all of
which must be kept in step; the lead's ruling is to fix it once, at the source.

`TableType` gains `sqlName`, the single authority for what clients are told:

  Regular -> TABLE   ·   View -> VIEW   ·   MaterializedView -> MATERIALIZED_VIEW
  External -> EXTERNAL   ·   Changelog -> CHANGELOG   ·   Enrichment -> ENRICHMENT

`sqlName` is ABSTRACT on the sealed trait on purpose: a seventh table type cannot compile
until someone decides what clients should call it. That is stronger than a test, and it is
precisely the default that let `REGULAR` ship.

A materialized view keeps its OWN type rather than collapsing into `VIEW` (AD-A-6): it has
storage, a refresh schedule and a watcher, and JDBC permits arbitrary type strings.

STORAGE IS UNTOUCHED. `TableType.name` remains the `_meta.type` key, `TableType.apply`
still parses only the stored name, and an existing index whose mapping says `"regular"`
still reads back as `Regular`. No reindex, no migration.

Evidence, both halves of AC 10:
- `TableTypeVocabularySpec` (sql, 8 tests) enumerates `TableType` by walking the COMPILED
  package - the trait is sealed, so the walk is complete and needs no allow-list - asserts
  the stored name and the client name for every type, pins `Regular.sqlName != "REGULAR"`
  literally, and asserts the round-trip `TableType(t.name) == t`.
- `ShowTablesTableTypeSpec` (core, 4 tests) drives the real `TableExecutor` projection
  Docker-free from mapping JSON carrying the STORED `_meta.type`, covering all six types,
  the no-`_meta` default and MV-vs-VIEW.

Falsified: reverting the projection turns 3 of 4 core tests red; `Regular.sqlName =
"REGULAR"` turns 2 of 8 sql tests red; dropping one type from the expectation table turns
3 red with a clue naming it, which is what proves the walk is live.

Release note: the `SHOW TABLES` `type` column changes value for every plain index
(`REGULAR` -> `TABLE`), and materialized views become their own node type in BI browsers -
knowingly reversing part of story 20.3's PD-4, on the measurement that did not exist then.
The driver-side shims (arrow `normaliseTableType`, jdbc `contains("regular")` /
`contains("view")`) are deliberately NOT removed here: they come out at the repin onto a
published core carrying this change, never before (AD-A-7).
…jection

Independent review round. Five findings, all addressed here; nothing deferred.

1. `ResultRenderer.scala:225` renders the `SHOW TABLE <t>` header (REPL `\st <table>`)
   as `s"[${table.tableType}]"` - the case-object toString, `Regular` / `MaterializedView`.
   That is why no `tableType.name` search found it, and why the first commit called
   `GatewayApi.scala:554` the only display projection.

   Before this story both surfaces spoke the internal vocabulary and merely disagreed on
   case. Fixing one of them did not remove a divergence, it CREATED one - measurable in
   this repo's own walkthrough, where `SHOW TABLES` says `TABLE` at
   `documentation/sql/dql_statements.md:1315` and `SHOW TABLE users` said `[Regular]` 35
   lines below at :1350; same pairing at `documentation/client/repl.md:967` versus :846.

   Both projections now read `TableType.sqlName`, the two doc lines are updated, and three
   new tests pin the header per type, pin that it is never the Scala name, and pin that it
   AGREES with the `SHOW TABLES` type column for every type.

   Lesson worth keeping: enumerate a concept's renderings BY TYPE, not by expression. A
   grep for the expression you are replacing is blind to the interpolation of the value
   itself.

2. The new scaladoc asserted that `name` "is never a display value". That is falsified 80
   lines below it: `Table.merge` throws `Cannot alter table <t> of type ${tableType.name}`,
   a user-facing message. So `name` has THREE consumers, not the two the design gate
   counted - and that count was the justification for the storage/display split being safe.

   The message stays: an engine refusing an operation on its own construct legitimately
   names the engine's own type. What changes is that the exception is now WRITTEN DOWN, in
   `TableType`'s scaladoc and at the throw site, with the rule that a new read of `name`
   outside `_meta.type` and that message is a design change. Silence about a projection is
   exactly what let `REGULAR` reach clients.

3. elasticsql had no live-cluster assertion of the new value, and ES 6.8 had none anywhere
   (the only real-ES assertion lives in extensions, inside an `assume` that 6.8 cancels).
   `GatewayApiIntegrationSpec` (template) now asserts `show_users -> TABLE`. Green on all
   five clients: ES 6.8 rest 73+1 pre-existing cancel, 6.8 jest 73+1, 7.17 74/74,
   8.18 74/74, 9.0 74/74.

4. `ShowTablesTableTypeSpec` hand-listed the six types, so a seventh would redden the sql
   vocabulary spec and leave the projection guard green - silently under-covering the thing
   it exists to guard. The sealed-hierarchy walk is extracted to `TableTypeEnumeration` and
   BOTH specs derive from it. `core` reaches it because `core -> macros -> sql` carries
   `test->test`.

5. One of the walk's exits swallowed `ClassNotFoundException` while the other two failed
   loudly, contrary to its own comment. All five exits now fail: no root, non-`file:` root,
   unloadable class, non-`object` subtype, empty result. The scaladoc says when a `jar:`
   root actually happens - running these assertions against a published `softclient4es-sql`
   jar - rather than leaving the reader to guess.

Verified: sql 1043/1043, core 956/956, `+ sql/compile` `+ core/compile`,
`++ 2.12.20 {sql,core}/Test/compile`, lint. Falsified: reverting `:225` turns exactly the
three new header tests red.

Release note (extended): BOTH client-facing renderings change together - the `type` column
of `SHOW TABLES` and the `SHOW TABLE <t>` header, which printed `Table: users [Regular]`
and now prints `[TABLE]`.
…W, with a space

Lead ruling AD-A-6-SUPERSEDED, PM concurring. `TableType.MaterializedView.sqlName`
`MATERIALIZED_VIEW` -> `MATERIALIZED VIEW`. The stored `name` is untouched, as always.

The reason is self-consistency with our own SQL, NOT another engine's convention. Every
statement that names this object is spaced - `CREATE MATERIALIZED VIEW`,
`SHOW MATERIALIZED VIEW`, `SHOW MATERIALIZED VIEW STATUS`, `SHOW CREATE MATERIALIZED VIEW`,
`DESCRIBE MATERIALIZED VIEW`. An underscore in the `SHOW TABLES` `type` column would be the
only place the product spells its own object differently from the statement that creates
it. (The Postgres argument is deliberately NOT used: with the transports reporting `VIEW`,
no external tool reads this string, and the reviewer had already caught the previous
scaladoc citing Postgres's spaced form while shipping an underscore.)

The scaladoc is corrected on a second point it was getting wrong: `sqlName` is the ENGINE
surface, not the JDBC/Flight `TABLE_TYPE`. Those drivers map it onto the two values their
`getTableTypes` advertises, and for a materialized view that is `VIEW` - permanently, not as
a shim, because `getTables` filters by exact string match, so a third value with the
advertised list unchanged would make every materialized view vanish from an object browser.
That is jdbc#34's dead-end, and it is written at the case object so a future cleanup cannot
mistake the mapping for temporary.

One assertion had to change to accept the ruling, and it is the interesting part: the
well-formedness pin read `fullyMatch regex "[A-Z][A-Z_]*"`, which a space fails. It is now
`"[A-Z]+( [A-Z]+)*"` - which also REJECTS an underscore, so the same assertion that admits
the new spelling is what stops it silently regressing to the old one. A literal pin sits
beside it (`shouldBe "MATERIALIZED VIEW"`, `should not include "_"`), because a property
assertion is structurally blind to a spelling choice.

`Feature.fromString("MATERIALIZED_VIEWS")` in `FeatureFromStringSpec` was checked and left
alone: a licence feature identifier, unrelated to the table-type vocabulary, and the only
false positive in the repo. No documentation example shows a materialized view in a
`SHOW TABLES` `type` column, so no doc change was needed.

The anti-drift assertion added in the previous commit - the `SHOW TABLE` header agreeing
with the `SHOW TABLES` column - is kept and now agrees on the spaced value.

Verified: sql 1044/1044 (+1), core 956/956, `+ sql/compile` `+ core/compile`,
`++ 2.12.20 {sql,core}/Test/compile`, lint; `JavaClientGatewayApiSpec` 74/74 on real ES 8.18.
Falsified: restoring the underscore turns exactly 3 of 9 sql tests red - the expectation,
the new literal pin, and the well-formedness assertion.

Release note, now SMALLER: a BI tool observes NO change from Part D. A plain index already
reached a browser as `TABLE` through the driver's own mapping and a materialized view
already reached it as `VIEW`, so the BI-browser warning is withdrawn, and so is the claim
that this reverses story 20.3's PD-4 - PD-4's frozen advertised list is respected. What
changes is the engine surface: `SHOW TABLES` `type` goes `REGULAR` -> `TABLE` and
`MATERIALIZED_VIEW` -> `MATERIALIZED VIEW`, and the `SHOW TABLE` header goes
`[Regular]` -> `[TABLE]`.
@fupelaqu
fupelaqu marked this pull request as ready for review September 10, 2026 04:02
@fupelaqu
fupelaqu merged commit 7d0746c into main Sep 10, 2026
4 checks passed
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