feat(postgres): browse multiple databases on one connection#402
Draft
debba wants to merge 10 commits into
Draft
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.
Collaborator
|
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.
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.
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:
database, so they can hit the primary database (and the wrong schema). Add/edit column, drop index and drop foreign key are routed to the node's database in this PR.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.schemavia the global active schema.dump_databaseis bound to the selected database's pool (Postgres now counts as multi-database capable), but theschemait receives is the connection-wide active schema, so a dump taken while a different schema is active may be scoped incorrectly.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.