Skip to content

feat: implement get_schema_snapshot and batch metadata RPCs (#121) - #123

Merged
aesslinger merged 2 commits into
mainfrom
feat/121-schema-snapshot
Sep 29, 2026
Merged

aesslinger merged 2 commits into
mainfrom
feat/121-schema-snapshot

Conversation

@aesslinger

@aesslinger aesslinger commented Sep 29, 2026 •

Copy link
Copy Markdown
Collaborator

What

Implements the three batch metadata RPCs in src/handlers/metadata.rs that were stubs returning "method not found" (#121):

  • get_schema_snapshot — what the ER diagram window calls to fetch a schema's tables/columns/foreign keys in one round trip. Opening the ER diagram against this plugin rendered a blank canvas until the host-side fallback in tabularis#822 started composing the same shape from get_tables/get_columns/get_foreign_keys (O(number of tables) round trips instead of one).
  • get_all_columns_batch / get_all_foreign_keys_batch — the batch building blocks get_schema_snapshot composes. Not wired to anything on the host yet, but the same shape of gap; shipped alongside for consistency.

Ported the built-in driver's three functions byte-for-byte (tabularis/src-tauri/src/drivers/postgres/mod.rs):

  • get_all_columns_batch — batch analog of get_columns: same SELECT (reusing the already-parity-verified row_to_table_column mapper) minus the c.table_name = $2 filter, with c.table_name projected out, ordered by table_name, ordinal_position, grouped by table into the host's HashMap<String, Vec<TableColumn>> wire shape.
  • get_all_foreign_keys_batch — batch analog of get_foreign_keys: same SELECT (reusing a new row_to_foreign_key helper extracted from get_foreign_keys' inline mapper) minus the src_cl.relname = $2 filter, ordered by table_name, conname, src_attnum, grouped the same way.
  • get_schema_snapshot — composes get_tables + the two batch calls via a pure build_schema_snapshot, returning the host's Vec<TableSchema> wire shape ([{name, columns, foreign_keys}]). Mirrors the builtin's composition (including unwrap_or_default() → empty array for tables with no columns/FKs).

Refactored to avoid duplicating query text: extracted fetch_tables, fetch_all_columns/fetch_all_foreign_keys, row_to_foreign_key, and group_rows_by_table as shared helpers. No rpc.rs changes — all three methods were already dispatched on main, pointing at the stubs.

Wire-shape parity

Verified against the host's RpcDriver deserialization path (tabularis/src-tauri/src/plugins/driver.rs:1456-1551): the three result shapes it deserializes are Vec<TableSchema> and two HashMap<String, Vec<_>>. The TableColumn.is_generated field is #[serde(default)] on the host, so omitting it (as get_columns already does today) is wire-compatible — the host reads the missing field as false, matching the builtin's explicit is_generated: false.

Tests

  • Unit (metadata_tests.rs): 4 new build_schema_snapshot tests — zipping tables with their columns/FKs, table-order preservation from get_tables, empty-schema returns an empty array (not null/error), and dropping the table-level comment to match TableSchema's exact shape.
  • Live-DB (live_db.rs): 1 new self-contained test exercising all three RPCs against real PostgreSQL 16 (parent w/ PK + enum, child w/ FK + ON DELETE CASCADE, FK-free table), asserting the enum/PK/auto-increment column shapes, the FK ref_table/on_delete + absent-key behavior, and the snapshot's per-table {columns, foreign_keys} arrays.

Verification

  • cargo build --release
  • Cross-repo 83-test byte-for-byte parity suite (POSTGRES_PLUGIN_BIN against tabularis' src-tauri/tests/postgres_integration parity*): 83/83 pass — exercises the per-row mappers the new batch methods reuse via parity_get_columns/parity_get_foreign_keys/parity_get_tables, so no existing behavior regressed.
  • 335 unit + 26 live-DB (1 new) against the local pg-tabularis-test container.
  • cargo clippy --all-targets -- -D warnings; cargo fmt --all --check; npx markdownlint CHANGELOG.md — all clean.

Out of scope — filed separately

Hard verification surfaced a pre-existing parity divergence in the shared row_to_table_column mapper: the NULL-default filter is case-sensitive (d == "NULL") where the builtin is case-insensitive (eq_ignore_ascii_case("null")). For a column declared DEFAULT null (lowercase), the plugin emits a spurious default_value: "null" where the builtin omits it. The #121 batch methods inherit this via the shared mapper (consistent with the already-shipping get_columns). The cross-repo parity suite doesn't catch it (no DEFAULT null columns in the seed fixtures). Filed as #122 rather than folded in here for scope discipline; #121 is unaffected and ships the inherited behavior.

Closes #121.

Three batch metadata RPCs in src/handlers/metadata.rs were stubs returning
"method not found": get_schema_snapshot, get_all_columns_batch, and
get_all_foreign_keys_batch. get_schema_snapshot is the one that actively
broke a feature -- it's what the ER diagram window calls to fetch a
schema's tables/columns/foreign keys in one round trip; opening the ER
diagram against this plugin rendered a blank canvas until the host-side
fallback in tabularis#822 started composing the same shape from
get_tables/get_columns/get_foreign_keys (O(number of tables) round trips
instead of one). The other two aren't wired to anything on the host yet
but are the same shape of gap and ship alongside for consistency.

Ported the built-in driver's three functions byte-for-byte
(tabularis src-tauri/src/drivers/postgres/mod.rs):

- get_all_columns_batch: the batch analog of get_columns -- same SELECT
  (reusing the already-parity-verified row_to_table_column mapper) minus
  the c.table_name = $2 filter, with c.table_name projected out and rows
  ordered by table_name, ordinal_position, grouped by table name into the
  host's HashMap<String, Vec<TableColumn>> wire shape (a JSON object keyed
  by table name).
- get_all_foreign_keys_batch: the batch analog of get_foreign_keys -- same
  SELECT (reusing a new row_to_foreign_key helper extracted from
  get_foreign_keys' inline mapper) minus the src_cl.relname = $2 filter,
  with src_cl.relname projected out as table_name, ordered by table_name,
  conname, src_attnum, grouped the same way.
- get_schema_snapshot: composes get_tables + the two batch calls via a
  pure build_schema_snapshot helper, returning the host's Vec<TableSchema>
  wire shape ([{name, columns, foreign_keys}]). Mirrors the builtin's
  composition exactly -- get_tables for the table list and ordering, then
  unwrap_or_default() to an empty array for any table with no columns or
  FKs in the batch maps.

Refactored to avoid duplicating the per-table query text between the
batch and single-table handlers, and between the batch handlers and the
snapshot composition: extracted fetch_tables (shared by get_tables and
get_schema_snapshot), fetch_all_columns/fetch_all_foreign_keys (shared by
the batch RPCs and the snapshot), row_to_foreign_key (shared by
get_foreign_keys and get_all_foreign_keys_batch), and group_rows_by_table
(the shared table->rows grouping into the wire JSON object).

build_schema_snapshot and group_rows_by_table are pure functions split
out for unit testing without a live database, per the repo's
extract-pure-logic pattern (.rules/rust.md #4/#5).

Wire-shape parity verified against the host's RpcDriver deserialization
path (tabularis src-tauri/src/plugins/driver.rs:1456-1551): the three
result shapes it deserializes are Vec<TableSchema> and two
HashMap<String, Vec<_>>. The TableColumn.is_generated field is
#[serde(default)] on the host, so omitting it (as get_columns already
does today, and the batch methods now inherit via row_to_table_column) is
wire-compatible -- the host reads the missing field as false, matching
the builtin's explicit is_generated: false.

No rpc.rs changes: all three methods were already dispatched on main,
pointing at the stubs.

Tests: 4 new build_schema_snapshot unit tests (zipping tables with
columns/FKs, table-order preservation from get_tables, empty-schema
returns an empty array not null/error, and dropping the table-level
comment to match TableSchema's exact shape). 1 new live-database
integration test exercising all three RPCs against real PostgreSQL 16
(parent table with a PK + enum column, child with an FK + ON DELETE
CASCADE, and an FK-free table to exercise the empty-foreign_keys
fallback), asserting the enum/PK/auto-increment column shapes, the FK
ref_table/on_delete + absent-key behavior, and the snapshot's per-table
{columns, foreign_keys} arrays.

Verified: cargo build --release; the cross-repo 83-test byte-for-byte
parity suite (POSTGRES_PLUGIN_BIN against tabularis'
src-tauri/tests/postgres_integration parity*) passes 83/83 -- this
exercises the per-row mappers the new batch methods reuse via
parity_get_columns/parity_get_foreign_keys/parity_get_tables, so no
existing behavior regressed; 335 unit + 26 live-DB (1 new) against the
local pg-tabularis-test podman container; cargo clippy --all-targets
-D warnings; cargo fmt --all --check; npx markdownlint CHANGELOG.md --
all clean.
@aesslinger aesslinger added enhancement New feature or request prerelease:rc Version suggestion targets a release candidate labels Sep 29, 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.

@aesslinger aesslinger self-assigned this Sep 29, 2026
@aesslinger
aesslinger merged commit b8889f9 into main Sep 29, 2026
7 checks passed
@aesslinger
aesslinger deleted the feat/121-schema-snapshot branch September 29, 2026 16:32
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.

get_schema_snapshot (and get_all_columns_batch/get_all_foreign_keys_batch) are not implemented

1 participant