feat(postgres): browse multiple databases on one connection#402
Conversation
Lets a PostgreSQL connection hold and browse several databases at once, the same way MySQL/MariaDB connections already can. Because Postgres binds a connection to one database and cannot query across databases, each selected database gets its own pool and the sidebar shows a database to schema to table tree. Backend reads (get_tables, get_views, get_schemas, get_columns, etc.) and execute_query now take an optional database argument that routes the work to the right pool; the convention matches the existing record mutation commands. Empty selections fall back to the postgres maintenance database since Postgres cannot connect server-wide.
…nd tab persistence on multi-database connections On schema-based multi-database (PostgreSQL) connections the backend keeps a separate pool per database, so every table-scoped call must carry both the schema and the database. Several paths dropped the database, which made them hit the connection's primary database and fail with relation-not-found: - PK/column metadata (fetchPkColumn, new-row, insert validation) omitted the database, leaving pkColumns null and silently turning the grid read-only. Added buildTableRoutingParams() to centralize schema+database routing. - Related-records / FK navigation ran execute_query without the database and dropped the referenced schema. Added ref_schema to ForeignKey (Rust + TS, mapped from foreign_schema_name) so cross-schema FKs qualify correctly, and routed the related-records query to the tab's database. - Editor tabs lost their database on save: cleanTabForStorage didn't persist the field, so a restored tab queried the primary database. Also made findExistingTableTab database-aware so same-schema tables in different databases no longer reuse the wrong tab. Adds a multi-schema demo database (erp_demo: hr/inventory/sales + cross-schema FKs) and tests for the routing/persistence regressions.
…ulti-database connections Replace the per-schema tree nodes under a database with a single compact schema dropdown: pick one schema and its tables/views/routines render directly below, like TablePro. Adds a hideHeader mode to SidebarSchemaItem to render a schema's contents without its collapsible header, a resolveActiveSchema() helper (picked -> connection-active -> public/first), and optional triggerClassName/leadingIcon props on the shared Select so the sidebar trigger is compact with a schema icon. Adds the sidebar.schema key to all locales and tests for resolveActiveSchema.
The schema-based multi-database tree (database → schema → table) fetched table metadata against the right connection pool, but right-clicking a table dropped the database entirely: ContextMenuData and the context-menu payload only carried schema, so Show Data, New Console, Count Rows, Delete Table, View Schema, Generate SQL, Add Column, drop index/foreign key, and the ER Diagram all silently fell back to the connection's default database.
|
Reviewed locally (tests pass: frontend 140, backend pool_manager 36). The architecture is solid — per-database pool keying ( The one real issue is that Blockers
Nits
Repro for the three blockers, on a connection with multiple Postgres DBs selected: run a multi-statement query in a non-primary DB (B1), paginate a large table in a non-primary DB (B3 count), export a non-primary-DB table to CSV (B2) — each hits the primary pool instead. |
…resh sidebar after table drop Editing a timestamp/timestamptz/date/time/interval cell failed with "error serializing parameter N" because tokio-postgres infers a CAST($N AS type) placeholder's effective type from the cast target, rejecting a bound Rust String before PostgreSQL's own text-to-temporal parsing ever runs. The same root cause silently affected uuid-shaped string binds (#392 was only a partial fix, for varchar PK columns). Every bound PostgreSQL parameter now declares its wire type explicitly via prepare_typed/execute_typed, so the client-side type check matches what is actually sent and the CAST performs the real conversion server-side. Verified against a live PostgreSQL 16 container (docker), including the exact UPDATE from the report, with the fix landing in src-tauri/src/drivers/postgres/{binding,client,mod}.rs and a new ignored live-DB regression test. Fixes #401. Also: deleting a table from the sidebar context menu now refreshes the schema/database node that actually lists it (PostgreSQL schema-based and flat multi-database trees), instead of only the flat single-database table list — the dropped table no longer lingers until a manual refresh.
- ctxDatabase from the context-menu payload is string | null | undefined;
coerce null -> undefined so it satisfies the string | undefined sinks (tsc -b).
- Wrap schemaDataMap (databaseData?.schemaDataMap ?? {}) in useMemo so the
lazy-load effect's dependency is stable (react-hooks/exhaustive-deps).
… multi-db Opening New Console from a database node on a schema-based multi-database connection (PostgreSQL) produced a tab with schema=<databaseName> and no database. execute_query then ran on the connection's PRIMARY pool and issued SET search_path TO "<databaseName>" — but that name is a database, not a schema — so unqualified relations failed with relation-not-found. newConsoleForDatabase now takes isSchemaBased: for schema-based drivers it routes via database (no bogus schema); flat drivers (MySQL) keep overloading schema as the database name. The sidebar threads spec.database into runQuery.
…ti-db On a schema-based multi-database connection (PostgreSQL) the editor treated a console tab like the flat MySQL layout: the active-database dropdown, new console/notebook creation, and Convert to Console all set tab.schema to the database name and never set tab.database. execute_query then ran on the connection primary pool and issued SET search_path TO "<databaseName>" (a database is not a schema), so console queries failed with relation-not-found (e.g. SELECT * FROM "public"."app_meta" against the wrong database). Schema-based connections now route a non-table tab via tab.database (the pool key) and keep tab.schema as the real schema; flat drivers keep overloading schema with the database name. Covers: the toolbar db dropdown (label, active highlight, selection), new console/notebook defaults, Convert to Console (inherits both schema and database from the source tab), the tab label and the window title.
Follow-up fixes for schema-based multi-database (PostgreSQL) routing in the results grid: - Commit (Ctrl+S) fallback no longer sends the PostgreSQL schema name as database on schema-based drivers: isMultiDatabaseCapable now includes Postgres, so the flat-driver fallback fired on plain single-database connections and routed writes to a pool for a database named after the schema (e.g. "public"), breaking every update/insert/delete commit. - Export CSV/JSON now routes via the tab's database on schema-based drivers instead of passing the PostgreSQL schema as the database name. - count_query accepts a database param and the frontend passes the tab's database, so Load Count runs on the correct pool instead of the primary. - execute_query_batch accepts the database param the frontend was already sending (Tauri silently dropped it), so multi-statement scripts on a non-primary database console tab run on the correct pool. - save_blob_to_file / fetch_blob_as_data_url accept a database param, and the tab's schema/database are plumbed through DataGrid -> RowEditorSidebar -> FieldEditor -> BlobInput so BYTEA preview/download hit the right pool and schema.
Results Grid — multi-database routing review + fixes (8664014)Verified every Results Grid checklist item at code level for Postgres multi-db routing. Most paths were already correct (pagination, sort, toolbar WHERE/ORDER BY/LIMIT, pending changes, FK navigation + related records panel, and the new typed bindings in Fixes
Verified as already correct
Known non-fixes (pre-existing, out of scope)
Checks
|
…leanup to the tab database Second round of multi-database (PostgreSQL) routing fixes: - Dump Database: schema-based drivers now fetch the table list for the exact schema the backend will dump (default public), routed to the target database's pool — previously databaseDataMap[db].tables was always empty for Postgres, so validation blocked every dump with 'no tables'. - Visual EXPLAIN / EXPLAIN ANALYZE: explain_query_plan accepts a database param and the modal receives the tab's database from the editor. - Sidebar context menus: view/routine/trigger items now carry their node's schema+database; Show Data, Count Rows, View/Edit/Drop View, routine and trigger View Definition / Edit / Drop, and column edit/delete all route to the node's database instead of the primary pool or the global active schema. drop_view/create_view/alter_view/get_view_definition/ get_view_columns/create_trigger/drop_trigger accept a database param; ViewEditorModal and TriggerEditorModal thread it through. - Import Database: import_database accepts a database param and the modal scopes the import to the database node it was launched from. - ER Diagram from a database node: pass the node as database (pool key) on schema-based drivers instead of letting the page treat the database name as a schema on the primary pool. - Saved queries / favorites / history re-run: on schema-based drivers the stored database now goes into the tab's database slot (the pool key) instead of overloading schema; Save Query defaults to the tab's database. - Pool cleanup: disconnect (and health-check failure) now closes every pool belonging to the connection id — one per selected database — instead of only the primary database's pool. New unit tests cover the key sweep.
Second round of multi-database routing fixes (02d2e77)Follow-up to the results-grid fixes in 8664014 — this commit addresses the remaining routing gaps found during verification of the sidebar, tooling and lifecycle paths: Fixed
PR body updatedThe "Known limitations / follow ups" section now reflects this work and adds the two remaining known gaps: AI Query Generation and Clipboard Import are not database aware. Skipped intentionally (declared out of scope in the PR)Create Table/View/Trigger/Index/FK from nested nodes, autocomplete, sidebar highlight, transactional commit, Checks
|
On schema-based drivers (PostgreSQL) both the ER diagram and the database dump are scoped to a single schema, but neither surface let the user choose which one — they silently used the connection's global active schema. - Dump dialog: a schema dropdown lists the target database's schemas (get_schemas routed to that database's pool), defaults to the active schema (then public), drives both the table list and the schema the backend dumps. - ER diagram window: same dropdown in the header re-scopes the diagram to another schema of the target database without reopening the window. The opener passes a schemaBased flag through open_er_diagram_window since the window has no access to the opener's driver capabilities. - MySQL and single-database drivers are unchanged (no picker shown).
Schema picker for ER diagram and Dump (3540f99)Follow-up on reviewer feedback: on PostgreSQL both the ER diagram and the dump were silently scoped to the connection's global active schema. Both surfaces now expose a changeable schema dropdown (schema-based drivers only — MySQL and single-database drivers are unchanged, no picker shown):
PR body's dump limitation ("scoped via the global active schema") is superseded by this commit for the picker flows. Checks: |
Closes #340.
What this does
You can now point a single PostgreSQL connection at several databases and browse them all from the sidebar, the same way MySQL and MariaDB connections already work. Pick the databases you want in the connection dialog's "Databases" tab and each one shows up as a top level node you can expand into its schemas and tables.
Why Postgres needed special handling
MySQL lets one connection see every database, so the old multi database code treated a database name as if it were a schema and qualified table names with it. Postgres does not allow that: a connection is bound to one database and you cannot query across databases in a single session. So this PR keeps a separate connection pool per database and presents a real database to schema to table tree instead of the flat database to table layout MySQL uses.
The pool keying already included the database name, so most of the plumbing was about telling the backend which database a given read or query should run against.
How it works
get_tables,get_views,get_schemas,get_columns,get_indexes,get_foreign_keys, the routine and trigger helpers) andexecute_query/execute_query_batchnow take an optionaldatabaseargument. When set, the params get pointed at that database and the matching pool is used. This mirrors the convention the record mutation commands (delete_record,update_record,insert_record) already follow, so it is purely additive.isMultiDatabaseCapableno longer excludes schema based drivers; a newisSchemaBasedMultiDbhelper distinguishes the hierarchical Postgres layout from the flat MySQL one.postgresmaintenance database, since Postgres cannot connect without a target. This is what makes "Load Databases" work before you have picked anything.UX change worth a look
Now that PostgreSQL counts as multi database capable, the connection dialog hides the single "database" field on the General tab and shows the "Databases" tab with a checklist instead, exactly like MySQL. Existing single database Postgres connections keep working, but the edit dialog looks different than before. Happy to keep the single field visible for Postgres if reviewers would rather not change that flow.
Known limitations / follow ups
These are not regressions, just things left out of this pass to keep the change focused on browsing and querying (updated after the two routing-fix commits on this branch):
database, so they can hit the primary database (and the wrong schema). Add/edit column, drop index/foreign key, and the view/routine/trigger context-menu actions (show data, view definition, edit, drop) are now routed to the node's database.AiQueryModalbuilds its schema context from the connection's primary database and the generated-query flows (including Visual-Explain deep links from the AI activity panel) carry nodatabase, so AI features target the primary database on multi-database Postgres connections.registerSqlAutocomplete/getTableColumnstake aschemabut nodatabase, soget_columnsruns against the connection's primary pool — completions for tables in a non-primary database are missing or wrong. It still assumes the flat MySQL shape.dump_databaseroutes to the selected database's pool and both the dump dialog and the ER diagram window now expose a schema dropdown (defaulting to the active schema, thenpublic) to pick which schema to dump/diagram. Dumping all schemas of a database in one file is still not supported.activeSchemafor both the database node and the schema node, so the highlight can be slightly off. Navigation works fine.Testing
postgres_dbnamefallback helper and for theisMultiDatabaseCapable/isSchemaBasedMultiDbchanges.tscand ESLint clean.PostgreSQL Editor Coverage — PR Verification Checklist
Sidebar — Tree & Object Types
Sidebar — Connection / Database actions
Sidebar — Schema-level actions
triggerscapability)Sidebar — Table context menu
Sidebar — Other object context menus
ref_schemaqualification)Sidebar — DDL modals (schema-aware)
Query Editor — Execution
:param) with input modalQuery Editor — Editing experience
Query Editor — AI & Analysis
Query Editor — Tabs & persistence
Results Grid — Display & navigation
Results Grid — Data editing (requires PK)
Results Grid — Postgres-specific types
Results Grid — Navigation & export
Known gaps — confirm intentionally out of scope for this PR