Skip to content

feat: implement get_table_ddl RPC method for dump support - #119

Merged
aesslinger merged 4 commits into
mainfrom
feat/118-get-table-ddl
Sep 29, 2026
Merged

aesslinger merged 4 commits into
mainfrom
feat/118-get-table-ddl

Conversation

@aesslinger

@aesslinger aesslinger commented Sep 27, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #118.

Problem

TabularisDB/tabularis#822 added a get_table_ddl method to the host's DriverTrait and dispatches it from RpcDriver as a "get_table_ddl" RPC call, so dump_database can write a schema-preserving dump for plugin-registered PostgreSQL connections. The RpcDriver side is wired, but this plugin had no handler for it, so dump-schema-structure failed with a "method not implemented" error.

Fix

Adds a get_table_ddl handler in src/handlers/metadata.rs, dispatched from rpc.rs. Given { params, table, schema }, it reconstructs a single CREATE TABLE statement from the table's columns — name, type, NOT NULL, PRIMARY KEY — mirroring the built-in driver's get_table_ddl (src-tauri/src/drivers/postgres/mod.rs) byte-for-byte: no defaults, indexes, or FKs, matching its existing scope rather than expanding on it.

  • fetch_table_columns is extracted out of get_columns (same query, same row_to_table_column mapping) so both handlers share one source of column metadata instead of duplicating the query.
  • build_table_ddl is the pure DDL-text assembly, split out for unit testing without a live database — 7 cases in src/handlers/metadata_tests.rs cover single/composite primary keys, no primary key, an empty column set (table not found), quote-escaping in the schema-qualified name, and quote-escaping in a column name.
  • Table/schema names, and now column names too, are quoted via the existing crate::utils::identifiers::quote_identifier/qualified helpers.

Hardening from a follow-up hard review

A /code-review max pass against the diff, cross-checked against the built-in driver's actual source, surfaced 13 candidate issues. 11 turned out to be real bugs, but 9 of those are byte-for-byte inherited from the built-in's own get_table_ddl/get_columns (enum/array type syntax, missing varchar(N)/numeric(p,s) bounds, composite-PK physical-vs-declared column order, no defaults/SERIAL/comments, a zero-column-table edge case, domain types). Per this repo's parity rule ("don't improve on it silently; behavioral differences are regressions here, not fixes"), those are intentionally left alone rather than diverging from the driver being ported — fixing them would need a coordinated change (or a deliberate parity-break decision) across both repos, not a unilateral patch here.

Two were fixed here, since they're specific to how this handler is wired up rather than inherited SQL-generation behavior:

  • Column-name escaping. Column names were spliced into the DDL as a bare "{name}" literal with no escaping, unlike the schema/table name a few lines below (which already goes through quote_identifier). A column named we"ird (legal PostgreSQL) produced a truncated, corrupted statement. Now reuses the same escaping helper for column names and PK columns.
  • Non-base-table rejection. dump_database normally lists tables via get_tables (already BASE TABLE-only), but an explicit table selection bypasses that and can name a view directly — get_table_ddl would silently return a fabricated, wrong CREATE TABLE instead of an error. Added a table_type check (ensure_base_table) that rejects anything that isn't an ordinary base table.

Also fixed as part of the same pass: the missing-table live test now asserts the actual error message instead of just "some error came back," and the CHANGELOG.md heading was corrected from ## Unreleased to this file's established ## [Unreleased] (bracketed) convention — verified via git log -p (11 prior bracketed headings vs. the one unbracketed occurrence this PR had introduced).

Testing

  • cargo test --lib: 337 pass.
  • cargo clippy --all-targets -- -D warnings, cargo fmt --all --check, and markdownlint all clean.
  • Exercised against a real postgres:16 instance (the same fixture tests/live_db.rs's CI job uses): 28 live tests pass (1 pre-existing pgvector test correctly skipped), including 3 new cases for get_table_ddl — DDL reconstruction from a live composite-PK table, the missing-table error path, and the new view-rejection guard against a real CREATE VIEW.

The host's dump_database now routes schema dumps through a DriverTrait
get_table_ddl method instead of matching on the driver string, but this
plugin had no handler for it, so dump-schema-structure failed with
"method not implemented" for plugin-registered PostgreSQL connections.

Reconstructs a CREATE TABLE statement from column metadata (name, type,
NOT NULL, PRIMARY KEY), mirroring the built-in driver's implementation
byte-for-byte. Extracts the column query get_columns already ran into a
shared fetch_table_columns helper, and splits the DDL text assembly into
a pure build_table_ddl function with unit test coverage.
@aesslinger aesslinger self-assigned this Sep 27, 2026
@aesslinger aesslinger added enhancement New feature or request prerelease:rc Version suggestion targets a release candidate labels Sep 27, 2026
@github-actions

Copy link
Copy Markdown

Version suggestion

Based on this PR's title (feat) and the prerelease:rc label:

Current 1.0.0-rc.4
Suggested next tag v1.0.0-rc.5

This is informational only — no tag or release is created automatically yet.

The unit tests in metadata_tests.rs only exercise the pure build_table_ddl
string builder with hand-fed column JSON — they never run the real query
in fetch_table_columns against an actual server, so the new RPC handler
had zero coverage of its live query path. Adds two cases to the existing
live_db.rs harness (spawns the built plugin binary and talks JSON-RPC
over stdio, same as tests/live_db.rs's other tests): a composite-PK table
whose reconstructed DDL is asserted byte-for-byte, and a nonexistent
table that must surface a JSON-RPC error.

Ran locally against a real postgres:16 container matching the CI harness
(POSTGRES_PLUGIN_BIN=target/debug/postgresql-plugin, port 54320) — both
new cases pass, and the full 27-test live suite plus 336 unit tests are
unaffected.
…et_table_ddl

A hard code-review pass against the built-in driver's own get_table_ddl
(tabularis/src-tauri/src/drivers/postgres/mod.rs) surfaced 13 candidate
issues. Most turned out to be byte-for-byte inherited from the built-in's
own implementation (enum/array type syntax, missing length/precision,
composite PK physical-vs-declared order, no defaults/comments, the
zero-column-table edge case) — per this repo's parity rule, those are
left alone rather than silently diverging from the driver being ported.

Two were genuine, fixable bugs specific to how this handler is wired up:

- Column names were spliced into the DDL as a bare `"{name}"` literal
  with no escaping, unlike the schema/table name three lines below (which
  already goes through the imported `quote_identifier`). A column named
  `we"ird` (legal PostgreSQL) produced a truncated, corrupted statement.
  Now reuses the same escaping helper for column names and PK columns.
- fetch_table_columns has no relkind/table_type filter. dump_database
  normally lists tables via get_tables (BASE TABLE only), but an explicit
  table selection bypasses that and can name a view directly — get_table_ddl
  would silently return a fabricated, wrong CREATE TABLE instead of an
  error. Adds a table_type check that rejects anything that isn't an
  ordinary base table.

Also: strengthens the missing-table live test to assert the actual error
message (it previously only checked that some error came back), adds a
live test proving the new view-rejection guard against a real view, and
fixes the CHANGELOG's "## Unreleased" heading to match this file's
established "## [Unreleased]" (bracketed) convention — confirmed via
`git log -p` showing 11 prior bracketed headings vs. this one unbracketed
occurrence.

Verified against a real postgres:16 container (same fixture the CI
"Live PostgreSQL integration" job uses): 337 unit tests, 28 live tests
(1 pre-existing pgvector test correctly skipped), clippy -D warnings,
fmt --check, and markdownlint all clean.
@aesslinger
aesslinger force-pushed the feat/118-get-table-ddl branch from 266e30d to e73c6d3 Compare September 27, 2026 14:19
# Conflicts:
#	CHANGELOG.md
#	src/handlers/metadata_tests.rs
#	tests/live_db.rs
@aesslinger
aesslinger merged commit a3c0f49 into main Sep 29, 2026
7 checks passed
@aesslinger
aesslinger deleted the feat/118-get-table-ddl branch September 29, 2026 16:44
aesslinger added a commit that referenced this pull request Sep 29, 2026
Ships the four pending PRs merged since 1.0.0-rc.4: get_table_ddl for
dump-schema-structure (#119), get_schema_snapshot + the batch metadata
RPCs for one-round-trip ER diagrams (#123, #121), session/transaction
pinning across execute_query/execute_query_batch runs (#124), and the
uuid 1.26.0 -> 1.26.1 patch bump (#108). Also documents the .tabularium
id/name split that landed since rc.4 (#117); the CI workflow split
(#116) is intentionally omitted per the rc.3/rc.4 convention of not
changelogging CI-only changes.

Verified: cargo build --release; 356 unit + 30 live-DB against the local
pg-tabularis-test podman container; the cross-repo 83-test byte-for-byte
parity suite (POSTGRES_PLUGIN_BIN against tabularis'
src-tauri/tests/postgres_integration parity*) passes 83/83; cargo
clippy --all-targets -D warnings; cargo fmt --all --check; npx
markdownlint CHANGELOG.md -- all clean.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request prerelease:rc Version suggestion targets a release candidate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: implement get_table_ddl RPC method for dump support

1 participant