feat: implement get_schema_snapshot and batch metadata RPCs (#121) - #123
Merged
Merged
Conversation
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.
Version suggestionBased on this PR's title (
This is informational only — no tag or release is created automatically yet. |
This was referenced Sep 29, 2026
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Implements the three batch metadata RPCs in
src/handlers/metadata.rsthat 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 fromget_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 blocksget_schema_snapshotcomposes. 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 ofget_columns: same SELECT (reusing the already-parity-verifiedrow_to_table_columnmapper) minus thec.table_name = $2filter, withc.table_nameprojected out, ordered bytable_name, ordinal_position, grouped by table into the host'sHashMap<String, Vec<TableColumn>>wire shape.get_all_foreign_keys_batch— batch analog ofget_foreign_keys: same SELECT (reusing a newrow_to_foreign_keyhelper extracted fromget_foreign_keys' inline mapper) minus thesrc_cl.relname = $2filter, ordered bytable_name, conname, src_attnum, grouped the same way.get_schema_snapshot— composesget_tables+ the two batch calls via a purebuild_schema_snapshot, returning the host'sVec<TableSchema>wire shape ([{name, columns, foreign_keys}]). Mirrors the builtin's composition (includingunwrap_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, andgroup_rows_by_tableas shared helpers. Norpc.rschanges — all three methods were already dispatched onmain, pointing at the stubs.Wire-shape parity
Verified against the host's
RpcDriverdeserialization path (tabularis/src-tauri/src/plugins/driver.rs:1456-1551): the three result shapes it deserializes areVec<TableSchema>and twoHashMap<String, Vec<_>>. TheTableColumn.is_generatedfield is#[serde(default)]on the host, so omitting it (asget_columnsalready does today) is wire-compatible — the host reads the missing field asfalse, matching the builtin's explicitis_generated: false.Tests
metadata_tests.rs): 4 newbuild_schema_snapshottests — zipping tables with their columns/FKs, table-order preservation fromget_tables, empty-schema returns an empty array (not null/error), and dropping the table-levelcommentto matchTableSchema's exact shape.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 FKref_table/on_delete+ absent-key behavior, and the snapshot's per-table{columns, foreign_keys}arrays.Verification
cargo build --releasePOSTGRES_PLUGIN_BINagainsttabularis'src-tauri/tests/postgres_integration parity*): 83/83 pass — exercises the per-row mappers the new batch methods reuse viaparity_get_columns/parity_get_foreign_keys/parity_get_tables, so no existing behavior regressed.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_columnmapper: theNULL-default filter is case-sensitive (d == "NULL") where the builtin is case-insensitive (eq_ignore_ascii_case("null")). For a column declaredDEFAULT null(lowercase), the plugin emits a spuriousdefault_value: "null"where the builtin omits it. The #121 batch methods inherit this via the shared mapper (consistent with the already-shippingget_columns). The cross-repo parity suite doesn't catch it (noDEFAULT nullcolumns 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.