Skip to content

feat: PostgreSQL plugin reaches parity + safety with built-in driver (#614) - #577

Merged
debba merged 83 commits into
TabularisDB:mainfrom
aesslinger:postgres-plugin-migration
Aug 14, 2026
Merged

feat: PostgreSQL plugin reaches parity + safety with built-in driver (#614)#577
debba merged 83 commits into
TabularisDB:mainfrom
aesslinger:postgres-plugin-migration

Conversation

@aesslinger

@aesslinger aesslinger commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Ref #16, closes #614

Summary

PostgreSQL now ships as a standalone plugin (TabularisDB/tabularis-postgresql-plugin) with full parity to the built-in driver, plus the host-side fixes needed for the plugin to actually be a safe, correct, first-class alternative — not just parity in isolation. This closes out the migration's Phase 0/1 work as a shippable unit.

What changed here:

  • #614 fixed — several core checks were hardcoded on driver === "postgres" and silently misbehaved for the plugin's "postgresql" driver id: broken identifier quoting, an SSL-mode dropdown that could silently connect in cleartext, wrong MCP schema defaults. Fixed across 9 commits, each independently verifiable — full breakdown and justifications on the issue thread, including a security-relevant gap found and closed during adversarial review of the fix itself before this was pushed.
  • In-tree plugin copy removed (plugins/postgres-plugin/) — stale relative to the extracted repo's beta releases; the extracted repo is now the source of truth, with its own security audit, CI hardening, and 5-platform release builds.
  • 24-item manual smoke test + full pnpm test/cargo test regression pass — done. Manual testing against the real plugin binary in the actual desktop UI surfaced one genuine bug outside Hardcoded driver === "postgres" checks in core break the standalone PostgreSQL plugin #614's scope: execute_query returned null for PostgreSQL enum values instead of the real label. Filed and fixed in the plugin repo (tabularis-postgresql-plugin#7), plus a new parity test here so the bug class can't silently reappear — parity suite is now 83/83 against the real plugin binary (v1.0.0-beta.3).
  • Synced with upstream/main (three times — 106 commits, then 15 more, then PR #588's merge) with zero unresolved conflicts each time; full regression suite reverified green after every sync. See the comment below for how the Fix PostgreSQL visual query follow-ups #588 overlap was resolved.
  • CI golden-file tautology fixedpg-integration.yml was setting REGENERATE_GOLDEN=1 unconditionally, which writes fresh output before comparing against it, so the assertion could never catch drift. Regeneration is now workflow_dispatch-only and opt-in.
  • Cleaned up superseded/out-of-scope planning docs that had accumulated on this long-lived branch.

Sign-off checklist

Item Status Where
Security audit ✅ Done Plugin repo PR #3 — 4 real gaps found & fixed
CI hardening ✅ Done Plugin repo — manifest validation, cargo audit, release-binary smoke test, live-db integration test
Cross-platform build ✅ Done Plugin repo release.yml — 5-platform matrix, smoke-tested
#614 host-side driver checks ✅ Done See issue #614 for the full breakdown
24-item manual smoke test ✅ Done Real plugin binary, real desktop UI, every result cross-checked directly against the database via psql — not just the UI's own success indicators
pnpm test / cargo test regression ✅ Done Frontend and backend suites green (only pre-existing, unrelated failures, confirmed present on unmodified main)

Not in this release

  • Phase 2 (sequences, JSONB editing, extension type system — issue Better PostgreSQL Support #16's remaining scope) happens in the plugin repo directly, tracked at tabularis-postgresql-plugin#9.
  • Phase 3 (removing the built-in driver) is a full-team-consensus decision, not something this PR decides. Tracked at tabularis#631, which also covers deleting the postgres_integration test suite and this PR's pg-integration.yml workflow once the built-in driver they protect is actually gone.
  • Registry publication (plugins/registry.json entry + a local-file install path for install_plugin) — the plugin isn't installable via Settings > Plugins yet. Out of scope here; not yet tracked by an issue.

How to Validate Locally

# Start PG 16
docker run -d --name pg-parity -p 54320:5432 \
  -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=password \
  -e POSTGRES_DB=testdb postgres:16

# Seed
bash tests/fixtures/seed_postgres.sh

# Run baseline + golden tests (builtin only — must pass)
cd src-tauri && cargo test --test postgres_integration -- --include-ignored --test-threads=1

To re-check cross-repo parity against a real plugin build (manual, not CI):

# In a checkout of TabularisDB/tabularis-postgresql-plugin
cargo build --release

# Back in this repo
POSTGRES_PLUGIN_BIN=/path/to/tabularis-postgresql-plugin/target/release/postgresql-plugin \
  cargo test --test postgres_integration parity -- --include-ignored --test-threads=1
# Expect 83/83

Depends On

Extend the RpcDriver to forward save_blob_to_file and
fetch_blob_as_data_url to plugin processes via JSON-RPC.

Plugins that implement these methods can now handle binary data
export/preview. Plugins that do not implement them receive a graceful
fallback via is_method_not_found (same pattern as routines, triggers).
Extend the RpcDriver to forward get_materialized_views,
get_materialized_view_columns, get_materialized_view_definition,
and refresh_materialized_view to plugin processes via JSON-RPC.

Plugins that declare materialized_views capability can now serve
these queries. Plugins without support receive graceful fallbacks
via is_method_not_found (empty vec or unsupported error).
Add an optional type_mappings field to PluginManifest and ConfigManifest
that maps generic inferred types (e.g. DATETIME, JSON) to driver-specific
types (e.g. TIMESTAMP, JSONB).

The RpcDriver now overrides map_inferred_type to consult these static
mappings at lookup time. This avoids the need for an async RPC call in a
synchronous trait method.

Built-in drivers continue to use their direct trait overrides and declare
empty mappings. Existing plugins without type_mappings are unaffected
(serde default is an empty map, passthrough behavior is preserved).
- Add separate CI workflow (pg-integration.yml) with PG 16 service
- Add seed script (postgres_seed.sql + seed_postgres.sh) for test schemas
- Add integration test harness (postgres_integration/) with 22 tests:
  - schema_discovery: 4 tests (get_schemas, get_databases, get_tables)
  - column_metadata: 6 tests (PK, nullable, types, max_length, enum)
  - indexes: 4 tests (btree, unique, composite, primary key)
  - foreign_keys: 4 tests (basic, composite table, cross-schema, empty)
  - query_execution: 6 tests (basic SELECT, pagination, all types,
    null handling, DML affected_rows, batch session state)
- All tests use #[ignore] and require PG on port 54320
- Seed creates test_schema + other_schema + secondary database
- CI job is separate from main test job (temporary for plugin migration)
…database tests (WIP)

Adds remaining test modules for Phase 0 baseline. Some modules have
compilation errors due to API signature mismatches that need fixing:
- routines.rs: RoutineInfo has no specific_name field
- crud.rs: update_record takes (pk_map, col_name, value) not (data, pk_map)
- routines.rs: drop_routine and get_routine_definition signature differences

These will be fixed in the next commit.
Fix compilation errors from incorrect function signature assumptions:
- routines.rs: use RoutineInfo.routine_type (String, not Option)
- routines.rs: get_routine_definition takes routine_type param
- routines.rs: drop_routine takes routine_type not arg signature
- crud.rs: update_record takes (&pk_map, col_name, value) per-column
- crud.rs: delete_record takes &HashMap (borrow, not owned)
- triggers.rs: get_trigger_definition requires table_name param

All 56 integration tests now compile successfully.
All test modules compile and cover the full PostgreSQL driver API:
- schema_discovery: 4 tests
- column_metadata: 6 tests
- indexes: 4 tests
- foreign_keys: 4 tests
- views: 6 tests
- materialized_views: 4 tests
- routines: 6 tests
- triggers: 4 tests
- crud: 8 tests
- query_execution: 6 tests
- multi_database: 7 tests
- ddl_generation: 7 tests
- explain: 3 tests
- blob: 3 tests

Total: 72 integration tests covering every public method of the
PostgreSQL driver. All use #[ignore] and require PG on port 54320.
CI workflow (pg-integration.yml) runs them with --include-ignored.
- Fix null handling test: exclude col_uuid (has DEFAULT gen_random_uuid())
- Fix character_max_length test: accept None (driver doesn't populate it)
- Fix enum type assertion: driver returns enum('val1','val2') format
- Fix MV definition test: handle pre-existing driver bug gracefully
- Fix blob insert test: use BLOB wire format instead of hex string
- Fix trigger test: cleanup at start (idempotent against prior failed runs)
- Fix alter_view test: cleanup at start (same reason)

All 72 tests pass with --test-threads=1 against PostgreSQL 16.
Two tests were weakened in the prior commit to 'accept either behavior' —
this violates TDD principles. Tests must assert the EXACT behavior:

- character_max_length: Assert None explicitly (known driver limitation).
  The plugin must return None too. If the driver is fixed later, this test
  will correctly fail — prompting both test and plugin updates.

- MV definition: Assert the error explicitly (known driver bug on PG 16).
  The plugin must produce the same error. If the bug is fixed upstream,
  this test will correctly fail — signaling the spec has changed.

Principle: Tests ARE the specification. A passing test means the behavior
is correct. We never weaken a test to accommodate — we assert what IS.
Golden files record the exact output of driver methods against the seeded
test database. They serve as the parity contract for Phase 1 — the plugin
must produce output matching these files byte-for-byte.

Adds:
- golden_utils.rs: write_golden() and assert_golden() helpers
- golden.rs: 17 capture/compare tests covering schemas, tables, columns,
  indexes, FKs, views, MVs, routines, triggers, queries, explain, multi-db
- golden/ directory: 17 committed JSON snapshots

To regenerate golden files after driver changes:
  REGENERATE_GOLDEN=1 cargo test --test postgres_integration golden -- --include-ignored --test-threads=1

Total test count: 89 (72 integration + 17 golden)
Remove #[ignore] from the 4 existing PG integration tests:
- test_postgres_integration_flow
- test_postgres_batch_preserves_temp_table_and_transaction
- test_postgres_affected_rows_reported_correctly
- test_postgres_foreign_keys_via_pg_catalog

These tests soft-skip (eprintln + return) if PG isn't available, so they
won't break the main CI that doesn't have a PG service. They WILL run in
our pg-integration.yml workflow and in the standard cargo test flow when
a local PG is available.

MySQL tests remain #[ignore] (no MySQL in CI).

Total tests now running against PG: 89 (new suite) + 4 (existing) = 93
Includes:
- postgres-plugin-migration.md (original phased plan)
- postgres-plugin-migration-alt.md (TDD approach with multi-db from day 1)
- postgres-plugin/ directory (per-phase detailed docs)
- sqlite-improvements.md (SQLite driver audit)
- .markdownlint.json config for planning docs
Fixes critical and high issues from deep code review:

Critical:
- Use deterministic UUID in seed (fixed value, not gen_random_uuid())
  so golden files produce identical output across environments
- EXPLAIN golden test writes for documentation only (no exact assert) —
  plan costs/widths are volatile across PG versions and table stats

High:
- CRUD tests now clean up inserted rows (no state accumulation)
- Restore #[ignore] on existing integration_tests.rs PG tests
  (avoids 20s timeout penalty on normal cargo test; CI uses --include-ignored)
- CI workflow: add apt-get update before postgresql-client install
- Remove unused TABULARIS_TEST_PG env var from CI

Low:
- Golden files now include trailing newline (POSIX compliance)
- Remove unnecessary #[allow(dead_code)] on pg_params_secondary
- Rename plan docs (alt plan is now primary)
Extend the RpcDriver to forward save_blob_to_file and
fetch_blob_as_data_url to plugin processes via JSON-RPC.

Plugins that implement these methods can now handle binary data
export/preview. Plugins that do not implement them receive a graceful
fallback via is_method_not_found (same pattern as routines, triggers).
Extend the RpcDriver to forward get_materialized_views,
get_materialized_view_columns, get_materialized_view_definition,
and refresh_materialized_view to plugin processes via JSON-RPC.

Plugins that declare materialized_views capability can now serve
these queries. Plugins without support receive graceful fallbacks
via is_method_not_found (empty vec or unsupported error).
Add an optional type_mappings field to PluginManifest and ConfigManifest
that maps generic inferred types (e.g. DATETIME, JSON) to driver-specific
types (e.g. TIMESTAMP, JSONB).

The RpcDriver now overrides map_inferred_type to consult these static
mappings at lookup time. This avoids the need for an async RPC call in a
synchronous trait method.

Built-in drivers continue to use their direct trait overrides and declare
empty mappings. Existing plugins without type_mappings are unaffected
(serde default is an empty map, passthrough behavior is preserved).
…ration

# Conflicts:
#	src-tauri/src/plugins/driver.rs
The test_pool_isolation_between_databases and test_alter_view tests
failed intermittently in CI with 'connection closed' errors caused by
pool exhaustion under parallel test execution (89 tests sharing a
10-connection pool).

Add a retry_transient helper that retries up to 3 times with backoff
when the error matches known transient pool/connection messages. Apply
it to the multi-step queries in both affected tests.
Implements Phase 0 deliverable 0.3 — the parity test infrastructure that
runs identical trait method calls against multiple driver implementations
and asserts equivalent results.

The harness compares outputs via JSON serialization (serde_json::Value),
which works with any Serialize type without requiring PartialEq/Clone on
model structs and catches subtle serialization differences.

Phase 0: only DriverTarget::Builtin is registered — tests validate the
harness works correctly against the built-in driver.

Phase 1: with_plugin() adds a second target — tests then mechanically
prove the plugin produces identical outputs to the built-in driver.

13 parity tests covering: schemas, databases, tables, columns, foreign
keys, indexes, views, view definitions, materialized views, routines,
triggers, multi-database, and map_inferred_type.
Set RUST_TEST_THREADS=4 in the pg-integration workflow. With 100+
tests sharing a 10-connection pool, unrestricted parallelism causes
random 'connection closed' errors as tests race for connections.

4 threads gives reliable results: enough parallelism to keep wall-clock
time short (~3s) while staying well within the pool's capacity.
With 100+ tests sharing global connection pools (keyed by params), even
4 threads caused intermittent 'connection closed' errors. The tests
complete in ~8s sequentially — negligible CI impact — and the flakiness
is eliminated entirely.

Also add retry logic inside the parity harness for defense in depth.
ForeignKey struct uses 'column_name' not 'column'.
Add 10 new golden capture tests covering:
- get_view_columns_active_users
- get_mv_definition (materialized view definition)
- get_mv_columns (materialized view columns)
- get_routine_parameters_add_numbers
- get_routine_definition_add_numbers
- get_trigger_definition_audit
- execute_query_with_pagination
- explain_analyze
- count_query

CI now runs with REGENERATE_GOLDEN=1 and uploads the golden/ directory
as an artifact. Once downloaded and committed, all golden assertions
will be active.
The built-in driver's get_materialized_view_definition fails with
'error serializing parameter 0' on PG 16 (regclass cast bug). The
golden test now captures whatever the driver returns (success or error)
as the parity expectation.
9 new golden snapshots capturing the built-in PostgreSQL driver output:
- get_view_columns_active_users.json
- get_mv_columns.json
- get_mv_definition.json (captures known regclass error)
- get_routine_parameters_add_numbers.json
- get_routine_definition_add_numbers.json
- get_trigger_definition_audit.json
- execute_query_with_pagination.json
- explain_analyze.json
- count_query.json

Total golden files: 26 (17 existing + 9 new).
These serve as the parity contract for Phase 1.
Phase 0 is done:
- 102 integration tests passing in CI (target was 70+)
- 26 golden files committed (covers all data-retrieval methods)
- Parity harness with dual-target support ready for Phase 1
- 2 consecutive green CI runs

DDL golden files removed from scope: DDL generation is validated
structurally in ddl_generation.rs, not by byte-exact golden match.
Exact-match goldens for SQL output create brittle tests that break on
whitespace without catching real bugs.
…risDB#614)

Widens the driver parameter on visualQuery.ts (Visual Query Builder SQL
generation), autocomplete.ts (Monaco SQL completion), foreignKeys.ts /
useReferencedRecord.ts / RelatedRecordsPanel.tsx (foreign-key row
preview), and databaseObjectActions.ts / objectPaletteItems.ts /
quickNavigator.ts (command palette and sidebar context-menu object
navigation) to accept a PluginManifest/DriverCapabilities object, not
just a bare driver id string.

Each caller already had activeCapabilities (or an equivalent
connectionData.capabilities) in scope — this closes the last capability-
driven gaps in the identifier-quoting chain: a postgres-compatible driver
registered under a different id now quotes identically to the builtin
"postgres" driver in autocomplete suggestions, visual query generation,
FK row preview, and every object-navigation action (new console, count
rows, show data).
Adversarial security review of the TabularisDB#614 SSL-mode fixes surfaced a real,
currently-live gap (confirmed empirically, not just theoretically):
DriverCapabilities.sql_dialect was typed as a plain SqlDialect with
#[serde(default)] backed by impl Default -> Postgres. That default was
introduced pre-TabularisDB#614 for the frontend statement splitter, where
"unspecified means postgres" was the correct, harmless behavior at the
time. This branch's new SSL-mode dropdown check and stale-value
migration then read that same field expecting it to distinguish
"explicitly declared postgres" from "said nothing" -- but by the time a
plugin manifest reaches either of those checks, Serde has already
collapsed both cases to the identical Postgres value, so there was
nothing left to distinguish.

The Oracle plugin is a live example: it sets supports_ssl: true and
declares no sql_dialect. Before this commit, it would have been silently
routed into Postgres-style SSL mode values by the dropdown, and any
saved connection using it would have been rewritten by the migration --
both based on a default that was never actually declared.

Changes sql_dialect to Option<SqlDialect> with skip_serializing_if, so
"declared" and "absent" are genuinely distinguishable end to end: a
manifest that omits the field now deserializes to None and is omitted
from the JSON sent to the frontend, rather than arriving as the literal
string "postgres". The three builtin drivers now wrap their explicit
declarations in Some(...). The SSL dropdown check and migration were
already written to treat only Some(Postgres) as true; the change is
that None now actually means None. The frontend needs no changes --
sql_dialect?: Dialect was already optional there, and other consumers
(the statement splitter) keep their own explicit `?? "postgres"`
fallback at the point of use, unaffected by this change.

Verified via a temporary test (added, run, and removed -- not part of
this commit) that reproduced the Oracle scenario exactly: a manifest
capabilities JSON with supports_ssl: true and sql_dialect omitted now
deserializes to None and is dropped from re-serialized JSON, where it
previously arrived as "postgres". Added a permanent regression test
(migrate_connection_ssl_mode_leaves_a_resolved_driver_with_no_declared_dialect_alone)
covering the same scenario for the migration path.
…abularisDB#614)

The host/port grid rendered 4 columns for driver === "postgres" and 3
for everything else, so a non-builtin driver whose manifest declares
the postgres SQL dialect (e.g. the standalone PostgreSQL plugin, id
"postgresql") got a 3-column grid instead — the same two fields (host,
port) laid out with a different column count than the builtin driver
uses for them. Originally scoped out of this issue as purely cosmetic;
on reconsideration it's a real, visible layout inconsistency between
the builtin driver and the plugin, in the same category as everything
else this issue is fixing, so it's in scope too.

Reuses the isPostgresDialect check already added for the SSL mode
dropdown instead of introducing a second capability check.
Manual smoke testing of the plugin found a real bug: execute_query
returned null for a PostgreSQL enum column value, even though the
database held a genuine non-null value. Filed as
tabularis-postgresql-plugin#7 and fixed there (35a438a) — this adds
the missing parity test so the class of bug can't silently reappear.

The 82 existing parity tests only covered *writing* enum values
(insert_record/update_record binding) and enum *metadata*
(get_columns via pg_enum). Nothing exercised reading an enum value
back through execute_query, so a plugin that silently nulled out
enum SELECTs passed all 82 anyway.

Verified the test is a real regression guard, not just a happy-path
check: built the plugin at its pre-fix commit in an isolated git
worktree and confirmed assert_parity fails with the exact left="happy"
(builtin) vs right=null (broken plugin) mismatch. Rebuilt against the
current, fixed commit and confirmed it passes. 83/83 parity tests
green against the real plugin binary.
- Remove superseded/out-of-scope planning docs (postgres-plugin-migration-original.md,
  postgres-improvements.md, sqlite-improvements.md) and fix the phase-docs README's
  dangling link to the renamed master plan doc.
- Fix pg-integration.yml: CI was setting REGENERATE_GOLDEN=1 unconditionally, which
  writes fresh golden output *before* comparing against it — the assertion could
  never catch drift. Regeneration is now workflow_dispatch-only, opt-in, and uploads
  as an artifact for manual review instead of validating against itself.
@aesslinger aesslinger changed the title feat: PostgreSQL plugin — from built-in driver to standalone plugin (Issue #16) feat: PostgreSQL plugin reaches parity + safety with built-in driver (#614) Aug 13, 2026
@aesslinger
aesslinger marked this pull request as ready for review August 13, 2026 13:01
@kilo-code-bot

kilo-code-bot Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Files Reviewed (5 files — incremental since 7f1f755)

Incremental changes since previous review

  • src/utils/identifiers.ts — bare-string fallback in shouldQuoteIdentifiers now also matches "postgresql" (the shipped plugin's id, per PR Fix PostgreSQL visual query follow-ups #588), so callers without a manifest in scope still quote correctly. Comment updated accordingly.
  • src/utils/visualQuery.ts — removed now-redundant !driver guard in formatTableRef (formatSqlIdentifier already no-ops for null/undefined drivers); added formatAggregateArgument/formatHavingColumnRef/formatAlias so HAVING aggregate refs and SELECT aliases are identifier-quoted for postgres. Logic correctly preserves *, nested calls, complex expressions, and DISTINCT prefixes while quoting simple alias.col refs.
  • src/pages/Editor.tsx — 4 useCallback dependency arrays widened from activeCapabilities?.schemas to activeCapabilities (lint fix; safer re-run semantics, no behavioral bug).
  • tests/utils/identifiers.test.ts — updated expectation for bare "postgresql" string + new manifest/quoting cases.
  • tests/utils/visualQuery.test.ts — new coverage for postgres table quoting, alias quoting, and HAVING aggregate formatting (including COUNT(*), SUM(t1.col), COUNT(DISTINCT ...)).

Also pulled in via merge from main: PostgreSQL visual-query follow-ups (#588) and cargo fmt/unused-import cleanup across the postgres_integration test suite. No production Rust code changed in this incremental diff.

Notes: The incremental changes are correct and well-covered by tests. The HAVING/aggregate quoting handles the expected edge cases (*, nested calls, DISTINCT, complex expressions) without breaking. The "postgresql" bare-string fallback and the formatTableRef guard removal are both safe and behavior-preserving for the null/undefined driver path. No issues to address before merge.

Previous Review Summary (commit 7f1f755)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 7f1f755)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (focus: #614 capability-driven fixes)

Rust backend

  • src-tauri/src/commands.rs — stale ssl_mode spelling migration (idempotent, dialect-aware, builtin postgres excluded)
  • src-tauri/src/drivers/driver_trait.rssql_dialect -> Option<SqlDialect> (absent ≠ postgres)
  • src-tauri/src/drivers/{mysql,postgres,sqlite}/mod.rs — wrap explicit dialects in Some(..)
  • src-tauri/src/mcp/mod.rsresolve_default_schema keyed off dialect (3 call sites)
  • src-tauri/src/mcp/tests.rs

Frontend (#614 identifier-quoting + SSL + layout)

  • src/utils/identifiers.tsshouldQuoteIdentifiers/getQuoteChar/quoteIdentifier/quoteTableRef/formatSqlIdentifier accept manifest/capabilities
  • src/utils/connections.tsgetDefaultPort/getDriverLabel accept PluginManifest
  • src/utils/{filterBar,tableToolbar,sidebarTableItem,autocomplete,databaseObjectActions,editor,foreignKeys,newConsole,objectPaletteItems,quickNavigator,visualQuery}.ts
  • src/components/modals/NewConnectionModal.tsx — SSL dropdown, host/port grid keyed off isPostgresDialect
  • src/components/modals/TriggerEditorModal.tsx
  • src/components/ui/{TableToolbar,RelatedRecordsPanel,VisualQueryBuilder}.tsx
  • src/components/layout/ExplorerSidebar.tsx + sidebar/{SidebarColumnItem,SidebarTableItem,SidebarViewItem}.tsx
  • src/hooks/{useReferencedRecord,useDatabaseObjectNavigation,useCommandPaletteObjectItems,useSqlAutocompleteRegistration}.ts
  • src/pages/Editor.tsx

CI / tests

  • .github/workflows/pg-integration.yml — golden regeneration now workflow_dispatch-only (tautology fixed)
  • src-tauri/tests/postgres_integration/* (parity, golden, enum-value regression), tests/fixtures/postgres_seed.sql, seed_postgres.sh

Notes: The capability-driven refactor is consistent end-to-end: security-relevant checks (SSL dropdown + stale-value migration, MCP public default) require an explicit Some(Postgres)/sql_dialect === "postgres" declaration and correctly treat an unspecified dialect as non-postgres (commit 65 closes the Oracle-style silent-cleartext gap). The historical postgres-default for the statement splitter / identifier quoting is preserved at its points of use, with the asymmetry clearly documented. The SSL migration is idempotent, scoped to non-builtin postgres-dialect connections, and re-saves only when a rewrite occurs. Golden-file CI no longer regenerates before comparing. All reviewed changes look correct and well-covered by tests.


Reviewed by glm-5.2 · Input: 25.2K · Output: 4.4K · Cached: 245.5K

…ration

Resolves the shouldQuoteIdentifiers/visualQuery.ts conflict with TabularisDB#588 per
debba's instructions on that PR: keep the sql_dialect-based capability check
as the primary path, but widen the literal-string fallback to also cover
"postgresql" so TabularisDB#588's original driver-id fix isn't lost when no manifest is
in scope. Also widens formatAggregateArgument/formatHavingColumnRef/
formatAlias/generateHavingClause (all new in TabularisDB#588) to the shared DriverArg
type so they inherit the same capability-driven quoting, verified against a
postgres-dialect plugin manifest producing byte-identical HAVING-clause
output to the bare "postgres" string.
@aesslinger

Copy link
Copy Markdown
Contributor Author

upstream/main synced — PR #588 merged as a prerequisite, conflict resolved per @debba's instructions

PR #588 (fix-postgres-vqb-followups) merged to main and has now been merged into this branch. As flagged in my earlier comment on that PR, it touched the same shouldQuoteIdentifiers function in src/utils/identifiers.ts that #614's fix changed here, so the merge produced real conflicts in 3 files (src/utils/identifiers.ts, tests/utils/identifiers.test.ts, tests/utils/visualQuery.test.ts; src/utils/visualQuery.ts auto-merged but needed a follow-up pass — see below).

@debba's resolution decision: merge #588 first (its HAVING/alias-quoting fixes are net-new, real bugs users hit today, no reason to hold them behind this larger migration), then resolve the conflict here by keeping the sql_dialect-based capability check as the long-term version of shouldQuoteIdentifiers, making sure the literal-string fallback still covers "postgresql" so #588's original fix isn't lost, and widening the new helper functions' driver params in the same pass.

What was done, matching each instruction:

  1. Kept the sql_dialect-based check as primary. shouldQuoteIdentifiers still checks capabilities.sql_dialect first when a manifest/capabilities object is available — unchanged from this branch's Hardcoded driver === "postgres" checks in core break the standalone PostgreSQL plugin #614 fix.
  2. Fixed the string fallback to cover "postgresql". The initial merge resolution (mine, before catching this) had left the no-manifest fallback as bare driver === "postgres" — which would have silently dropped Fix PostgreSQL visual query follow-ups #588's fix for any caller that only has a driver id string in scope, no manifest. Caught this against debba's comment before committing and fixed it to driver === "postgres" || driver === "postgresql". Updated the one test that had asserted the old (wrong) behavior.
  3. Widened the new helpers in the same pass. formatAggregateArgument, formatHavingColumnRef, formatAlias (all net-new from Fix PostgreSQL visual query follow-ups #588), and generateHavingClause's driver param were typed string | null | undefined after the auto-merge — narrower than this branch's DriverArg union (string | PluginManifest | DriverCapabilities | null | undefined). Widened all four to DriverArg so they inherit the same capability-driven quoting as everything else in the module.
  4. Verified end-to-end, not just by type-checking: added a throwaway test (run, confirmed passing, then deleted — never committed) proving a postgres-dialect plugin manifest produces byte-identical HAVING clause output to the bare "postgres" string via generateHavingClause, and that "mysql" correctly stays unquoted. This is the exact scenario Hardcoded driver === "postgres" checks in core break the standalone PostgreSQL plugin #614 exists to fix — a plugin driver id that isn't the literal string "postgres" — now proven to work for Fix PostgreSQL visual query follow-ups #588's new HAVING/alias code too, not just the pre-existing quoting paths.

Verification after resolving:

  • pnpm tsc --noEmit — clean
  • pnpm vitest run tests/utils/identifiers.test.ts tests/utils/visualQuery.test.ts — 110/110 passing (both PRs' test cases preserved, nothing dropped from either side)
  • Full frontend suite: 3717/3750 passing (33 failures are pre-existing/environmental — localStorage.clear() undefined in this local test environment — independently confirmed present on a clean, unmodified upstream/main checkout before this merge, unrelated to either PR)
  • pnpm tsc --noEmit + full suite re-run post-commit to confirm the committed state matches what was verified pre-commit

@aesslinger

Copy link
Copy Markdown
Contributor Author

@debba — this is ready for your final review.

Summary: PostgreSQL now ships as a standalone plugin with full parity to the built-in driver, plus the host-side fixes (#614) needed for the plugin to be a safe, correct, first-class alternative — not just parity in isolation.

Status:

  • Hardcoded driver === "postgres" checks in core break the standalone PostgreSQL plugin #614 fixed (9 commits, each independently verifiable — identifier quoting, SSL-mode dropdown, MCP schema defaults, plus a security-relevant gap found and closed during self-review)
  • 24-item manual smoke test done against the real plugin binary in the actual desktop UI, every result cross-checked directly against the database
  • Full pnpm test / cargo test regression pass green (only pre-existing, unrelated failures)
  • Synced with main three times, most recently to pull in #588 — that conflict was resolved per your instructions on that PR (kept the sql_dialect-based check, restored the "postgresql" string fallback, widened the new HAVING/alias helpers to the same capability-driven type); details in this comment
  • CI green (test, test-postgres), no merge conflicts with main

Deferred/out-of-scope items (Phase 2, Phase 3 deprecation decision, registry publication) are tracked separately — see the "Not in this release" section in the PR description.

Let me know if you'd like anything else looked at before merging.

@debba

debba commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

@aesslinger Great work overall. I verified this PR locally against PostgreSQL 16 and the actual v1.0.0-beta.3 Linux release binary: the cross-driver parity suite is genuinely 83/83 green, and the full frontend/backend suites pass.

Before approval, I think these items should be addressed:

  1. The parity harness can false-green when the plugin path is wrong. In src-tauri/tests/postgres_integration/parity.rs, setting POSTGRES_PLUGIN_BIN to a nonexistent file logs a warning, skips the plugin, and runs only against the builtin driver. I reproduced this with:

    POSTGRES_PLUGIN_BIN=/tmp/definitely-missing-plugin \
      cargo test --test postgres_integration parity_tests::parity_get_databases \
      -- --include-ignored --test-threads=1

    The test passes. If the variable is explicitly set, a missing/unstartable binary should fail immediately.

  2. Missing golden files are silently accepted. assert_golden() in golden_utils.rs returns successfully when a fixture does not exist. Outside explicit regeneration mode, a missing/renamed golden file should fail so accidental deletions and filename typos cannot leave CI green.

  3. Four React hook dependency warnings remain in Editor.tsx. The callbacks now consume the full activeCapabilities object but only list activeCapabilities?.schemas in their dependency arrays (around lines 345, 1145, 1540, and 2175). ESLint reports four react-hooks/exhaustive-deps warnings; this also conflicts with .rules/react.md rule 1 and can retain stale quoting capabilities.

  4. Formatting/warning cleanup: cargo fmt --all -- --check currently fails on PR-added/modified Rust code, the PostgreSQL integration target emits 50 unused-import/variable warnings, and git diff --check reports an extra blank line at EOF in src-tauri/src/mcp/tests.rs.

Verification performed:

  • pnpm test -- --run: 3750/3750 passed
  • cargo test --lib: 1142 passed, 4 ignored
  • PostgreSQL 16 integration suite: 181/181 passed
  • Actual plugin v1.0.0-beta.3 parity: 83/83 passed
  • pnpm exec tsc --noEmit: passed

The functional work looks solid; my request-changes recommendation is about making the new safety gates reliably fail when their prerequisites or fixtures are missing, plus the repository-rule cleanup above.

@debba

debba commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

@aesslinger One additional, non-blocking question: do you want to keep the .github/planning/ documents in the repository after this PR merges as historical/project documentation, or would you prefer to remove the completed migration plans before merge? They would remain available in Git history either way, while removing them would keep the current tree focused on active documentation. I do not have a strong preference, but it would be useful to make that choice explicit.

Phase 1 planning is done and Phase 2/3 now live in linked issues
(tabularis-postgresql-plugin#9, tabularis#631) rather than static docs on
this branch. History is preserved in git; removing keeps the tree focused
on active documentation, per debba's PR TabularisDB#577 review.
try_plugin_driver() treated "path doesn't exist" the same as "env var
unset" — both silently fell back to builtin-only, so a typo'd or stale
POSTGRES_PLUGIN_BIN made parity tests pass trivially instead of catching
that the plugin was never actually exercised. An explicit env var is a
request to test against a real plugin; a bad path for it should panic,
not degrade to the same outcome as not setting it at all.

Found by @debba during PR TabularisDB#577 review, reproduced with:
  POSTGRES_PLUGIN_BIN=/tmp/definitely-missing-plugin \
    cargo test --test postgres_integration parity_tests::parity_get_databases \
    -- --include-ignored --test-threads=1
which now fails as expected instead of passing.
A missing fixture (deleted, renamed, or a typo'd filename) was silently
skipped with a warning instead of failing the test — CI would stay green
even though nothing was actually verified. Missing golden files are now
a hard panic, with the actual captured output included in the message so
a developer intentionally adding a new golden case doesn't need a second
run with REGENERATE_GOLDEN=1 just to see what to commit.

Found by @debba during PR TabularisDB#577 review; verified by temporarily renaming
get_schemas.json and confirming the test now fails instead of skipping,
then restoring it and re-running the full 181-test suite clean.
Four callbacks read the full activeCapabilities object in their body
(via activeCapabilities ?? activeDriver, for capability-driven identifier
quoting) but only listed activeCapabilities?.schemas in their dependency
array — a leftover from before those call sites were widened for TabularisDB#614.
A capabilities update that doesn't change .schemas (e.g. sql_dialect
changing on manifest reload) would keep the callback's stale closure,
silently using outdated quoting behavior. Violates .rules/react.md rule 1.

Found by @debba during PR TabularisDB#577 review at lines 345, 1145, 1540, 2175;
confirmed with `npx eslint src/pages/Editor.tsx` before and after.
git diff --check flagged this. Found by @debba during PR TabularisDB#577 review.
Only our own added code in this file — not a whole-file reformat, since
the rest of commands.rs carries pre-existing formatting drift unrelated
to this branch (confirmed present on upstream/main before this PR).
…ion suite

Every file here was added wholesale by this branch and never run through
rustfmt before now, and 15 of them had an unused DatabaseDriver import left
over from earlier refactors. Both fixed together since they only differ by
whitespace/import lines — no logic changes. Confirmed via
`cargo build --tests --test postgres_integration` (0 warnings, was 50),
`cargo test --test postgres_integration --include-ignored` (181/181), and
the real-plugin parity run (83/83), all green before and after.

Found by @debba during PR TabularisDB#577 review.
@aesslinger

Copy link
Copy Markdown
Contributor Author

@debba — thank you for the thorough review, and sorry that #3 and #4 needed to be caught by you at all. Both were gaps in my own verification discipline (I was running tsc/cargo build/cargo test at every checkpoint but not eslint/cargo fmt --check alongside them) rather than anything ambiguous or hard to check — I've corrected that going forward and will run the full lint/fmt/warning gate every time, not just the compile/test gate.

Here's how each of your 5 items was addressed:

1. Parity harness false-greens on a bad POSTGRES_PLUGIN_BIN.
try_plugin_driver() in parity.rs now panics when the env var is set but the path doesn't exist, instead of silently falling back to builtin-only. Verified with your exact repro command — it now fails as expected instead of passing.

2. assert_golden() silently accepted missing golden files.
Now panics with the actual captured output included in the message (so adding a new golden case doesn't require a second run with REGENERATE_GOLDEN=1 just to see what to commit). Verified by temporarily renaming get_schemas.json, confirming the test now fails instead of skipping, then restoring it and rerunning the full 181-test suite clean.

3. 4 react-hooks/exhaustive-deps warnings in Editor.tsx (lines 345, 1145, 1540, 2175).
All 4 dependency arrays now list the full activeCapabilities object instead of just activeCapabilities?.schemas, matching what each callback body actually reads. Verified with npx eslint/pnpm lint — zero warnings, was 4.

4. Formatting/warnings.

  • The entire new postgres_integration/ test suite (38 files, never run through cargo fmt before) is now formatted, and 15 files' unused DatabaseDriver imports are removed — cargo build --all-targets now reports 0 warnings, was 50.
  • The EOF blank line in mcp/tests.rs is stripped (git diff --check clean).
  • One line in commands.rs (dialects.insert(...)) wrapped to satisfy rustfmt.
  • Found but not fixed here, flagged as a possible followup: commands.rs, driver_trait.rs, mysql/mod.rs, postgres/mod.rs, sqlite/mod.rs, and mcp/mod.rs each carry pre-existing cargo fmt drift unrelated to this branch — confirmed present on a clean upstream/main checkout before this PR, so it's not something this PR introduced. I deliberately didn't reformat those whole files since it felt out of scope for a fix PR (would bury our actual changes in unrelated diff noise), but wanted to surface it rather than silently leave it. Want a separate repo-wide cargo fmt PR to clear that debt, or leave it as-is for now?

5. .github/planning/ docs.
Removed — Phase 1 planning is done and Phase 2/3 now live in linked issues (tabularis-postgresql-plugin#9, #631) rather than static docs on this branch. Still in git history.

One new item surfaced during a final safety pass, filed as a follow-up rather than blocking this PR: #639 — the SSL-mode migration this PR adds only runs from the two GUI-process Tauri commands, never from the --mcp server process, which reads the same connections.json and can establish real connections using an unmigrated stale ssl_mode value. It's a pre-existing gap (the MCP process has never run any connection migration, including the older SSH one) that this PR's fix doesn't happen to close — not a regression this PR introduces. Given the scope of fixing it properly (extracting a path-based migration core usable from both the Tauri-command and MCP-bootstrap contexts), I think it's worth deciding post-merge rather than expanding this PR further. Your call on priority/timing.

Everything above is pushed. Full verification after all fixes: pnpm lint clean, pnpm tsc --noEmit clean, cargo build --all-targets 0 warnings, cargo fmt --check/cargo clippy clean on every file this PR touches, backend 1135+181+83 all green, frontend 3717/3750 (33 pre-existing/unrelated, confirmed present on clean upstream/main).

@debba

debba commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

Everything looks good to me now. I’d proceed with the merge and then start looking into #639, unless you’d prefer to take care of it yourself. Thanks for addressing all the feedback so thoroughly!

@aesslinger

Copy link
Copy Markdown
Contributor Author

Everything looks good to me now. I’d proceed with the merge and then start looking into #639, unless you’d prefer to take care of it yourself. Thanks for addressing all the feedback so thoroughly!

I think merge this and then I can start on the follow-ups.

@debba
debba merged commit f3a8d48 into TabularisDB:main Aug 14, 2026
3 checks passed
@aesslinger
aesslinger deleted the postgres-plugin-migration branch August 14, 2026 13:46
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.

Hardcoded driver === "postgres" checks in core break the standalone PostgreSQL plugin

2 participants