Skip to content

fix(editor): quote PostgreSQL table completion identifiers - #791

Open
mikevillari wants to merge 3 commits into
libredb:mainfrom
mikevillari:fix/postgres-table-completion-quotes
Open

fix(editor): quote PostgreSQL table completion identifiers#791
mikevillari wants to merge 3 commits into
libredb:mainfrom
mikevillari:fix/postgres-table-completion-quotes

Conversation

@mikevillari

@mikevillari mikevillari commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

PostgreSQL table suggestions insert catalog names verbatim, so accepting My_Schema_With_Caps.My_Table_With_Caps produces unquoted SQL that resolves to lowercase names. This also reproduces with the completion provider from before #715 (base 4c5f6577). Follow-up to the report in #705: #705 (comment).

Pass the editor's connection dialect into the completion provider and format PostgreSQL table-name components using PostgreSQL 18's conservative quote_ident rule. Ordinary lowercase names remain unquoted (authschema.user_authority); case-sensitive names retain their required quotes (authschema."UserAuthority", "USER_AUTHORITY"); keywords and special characters are escaped. The keyword categories come from PostgreSQL's parser keyword list, separate from the editor's suggestion list. The shared unconditional identifier-quoter keeps its existing semantics.

Keep display labels and filtering separate from inserted SQL, preserve qualified replacement ranges and bare-label fallback, recognize quoted table names when offering columns after a dot only for PostgreSQL, and re-register when the connection dialect changes. Other dialects keep their existing insertion behavior.

The quoting decision uses the catalog name, not the case of the typed prefix. PostgreSQL stores CREATE TABLE USER_AUTHORITY (...) as user_authority; its completion is bare. A table explicitly created as "USER_AUTHORITY" is a distinct object and its completion retains quotes.

Testing

  • Regression-first: the selective-quoting expectations failed against the original blanket-quoting implementation. Revised focused provider selection: 80 pass, 0 fail. New helper: 7/7 executable lines covered in the focused coverage run.
  • Native PostgreSQL 18.4 on macOS (verified on prior revision 1a63657a): 45 autocomplete-generated queries across public, authschema, and quoted "AuthSchema", with distinct marker rows verifying the selected object. 9 unquoted spelling controls also passed. Cache built from real information_schema names using the adapter's existing display-label convention; completion provider bundled from the revised source, Monaco word/range modeled for this matrix.
  • Formatter output matched native quote_ident for all 494 server keywords and 13 additional identifier cases.
  • Actual installed Monaco in an isolated browser fixture (prior revision 1a63657a): accepted suggestions and executed the resulting SQL against the same PostgreSQL instance for 7 cases: lowercase, uppercase typed prefix, mixed-case table, quoted uppercase table, quoted schema, keyword, and schema-dot trigger. All returned the intended marker. This is a real Monaco/provider interaction test, not full-application E2E.
  • Format, lint, typecheck, knip, chart/check, channels/showcase/check, readme/check, and security/check passed. Lint retains existing warnings.
  • Production build, library build, and package type-resolution check (attw) passed.
  • bun run test: 15,122 pass, 0 fail, including all 35 isolated component groups. Temporary local Helm 4.1.3, 7-Zip 26.03, and the locked chart dependency were installed before the successful complete run.

The implementation retains the provider's existing dot-delimited table-label representation. It does not redesign schema metadata, handle literal dots within individual identifier components, add completion of partially quoted input, or quote column suggestions. Full application E2E and the repository-wide coverage gate were not run locally; CI remains the merge gate.

AI-assisted implementation and validation.

Cross-database follow-up

Both new quoted table insertion and new quoted-name column lookup are restricted to PostgreSQL. The latter was initially shared; this revision restores the pre-PR bare-name lookup for every other dialect and callers without a dialect.

  • Regression-first: 17 compatibility cases failed before restricting the lookup; all pass afterward. The committed tests cover table insertion, qualified replacement, bare table/alias columns, previous delimiter behavior, and escaped PostgreSQL names.
  • Differential probe: 544 complete-suggestion comparisons match pre-PR 37fada54 across all 16 non-PostgreSQL registration settings plus the unspecified-dialect fallback (32 inputs each). This includes defensive registrations for non-SQL type IDs, not a claim that all 16 use the SQL editor. Insertion text, ranges, labels and other suggestion fields were compared. A separate 32-case PostgreSQL comparison matches prior revision 1a63657a. These use a modeled Monaco interface and a synthetic cache.
  • Component tests check PostgreSQL to MySQL/SQLite/DuckDB/SQL Server/Oracle and back, verifying old-provider disposal, new dialect registration, and unmount cleanup.
  • Live SQLite 3.51.0 and DuckDB v1.5.5: fresh native in-process databases accessed through the real LibreDB providers and their getSchema()/query() methods. 28 completion-generated queries returned expected marker rows (12 SQLite, 16 DuckDB), covering ordinary lowercase, mixed/uppercase names, direct/alias column completion, and DuckDB non-default-schema tables. Before/after suggestion objects matched. The cache mirrors QueryEditor and Monaco word/range is modeled; these are not browser UI checks.
  • Known limitations explicitly retained: 10 generated queries for special/keyword names were rejected as expected, with identical pre-PR/revised insertion text. Two manually quoted SQL controls succeeded, while both provider versions offer no quoted-dot column completion for these engines. SQLite's unchanged catalog reader also errors on a table containing an embedded double quote; that disposable fixture table was removed after recording the error so remaining checks could run. This PR does not repair those unrelated non-PostgreSQL limitations.
  • No live MySQL, SQL Server, Oracle, or other server-engine compatibility checks were run locally. Those dialects have completion-provider regression checks, not full end-to-end engine coverage.

@codecov

codecov Bot commented Sep 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@cevheri

cevheri commented Sep 11, 2026

Copy link
Copy Markdown
Member

I need your local test results regarding the following points:

  1. PostgreSQL tables can be defined entirely in uppercase or lowercase (e.g., user_authority / USER_AUTHORITY) and must be queryable without quotes.
  2. Tables might have been created with mixed-case names enclosed in quotes (e.g., "UserAuthority").
  3. The table might reside in the public schema or a different schema (e.g., public.orders, authschema.users).

@mikevillari

Copy link
Copy Markdown
Contributor Author

Verified locally against a real PostgreSQL 18.4 process, using the exact PR head 0a53b7e25c2dfc3d4018043dcf01fd6088a1f02f. No code change was needed for these cases.

Results: 36 autocomplete-generated queries passed, plus 21 control queries with the expected outcomes.

I created each of these in public, authschema, and additionally a quoted mixed-case schema "AuthSchema":

CREATE TABLE identifier Name stored in the catalog Unquoted table references PR autocomplete result
lowercase_table lowercase_table lowercase and uppercase both work quoted exact catalog name; query succeeds
UPPERCASE_TABLE uppercase_table lowercase and uppercase both work "uppercase_table"; query succeeds
"MixedCaseTable" MixedCaseTable fail with SQLSTATE 42P01, as expected "MixedCaseTable"; query succeeds
"QUOTED_UPPER_TABLE" QUOTED_UPPER_TABLE fails with SQLSTATE 42P01, as expected "QUOTED_UPPER_TABLE"; query succeeds

The important distinction in point 1 is how the table was created: unquoted CREATE TABLE USER_AUTHORITY stores user_authority, so both unquoted spellings resolve. Explicitly quoted CREATE TABLE "USER_AUTHORITY" creates a different, case-sensitive name that requires quotes. This PR quotes the name returned by the catalog; it does not make previously valid unquoted SQL invalid. It does insert optional quotes for ordinary lowercase catalog names.

Example queries actually generated and executed:

SELECT marker FROM "lowercase_table";
SELECT marker FROM "uppercase_table";
SELECT marker FROM public."MixedCaseTable";
SELECT marker FROM "authschema"."MixedCaseTable";
SELECT marker FROM "AuthSchema"."QUOTED_UPPER_TABLE";

For both public and authschema, controls also executed fully unquoted queries such as SELECT marker FROM public.UPPERCASE_TABLE and SELECT marker FROM authschema.lowercase_table successfully.

Method: create isolated fixture tables containing distinct marker rows; read their names from information_schema.tables; construct the cache with the same display-name convention as postgres.ts:1478 (bare name for public, schema-qualified otherwise); bundle and call this PR's actual registerSQLCompletionProvider; apply its returned replacement range and insertText; execute the resulting SQL and assert the exact expected marker. Covered empty-prefix selection, partially typed names, explicit public qualifiers, and non-public schema-dot suggestions. Used a modeled Monaco word/range interface, not the browser UI or the full database-provider connection flow. The pre-existing public-schema-dot popup and partially quoted input were not claimed as covered.

Server reported: PostgreSQL 18.4 on x86_64-apple-darwin24.6.0, compiled by Apple clang version 17.0.0, 64-bit. This was a temporary local native PostgreSQL server, not an in-memory SQL substitute. The server was stopped and its database removed after verification. AI-assisted execution and reporting.

@cevheri

cevheri commented Sep 11, 2026

Copy link
Copy Markdown
Member

What I meant to say was this:
Double quotes (") are not required for UPPER_CASE or lower_case table names.

@nktnet1

nktnet1 commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

What I meant to say was this: Double quotes (") are not required for UPPER_CASE or lower_case table names.

For UPPER_CASE, would that not become upper_case without the quotes?

Assuming the table was created externally with the screaming case, ofc, and the auto-completion is in libredb-studio.

@mikevillari

Copy link
Copy Markdown
Contributor Author

Understood — the previous implementation added unnecessary quotes to ordinary names, and my earlier test report only established that the quoted SQL executed. I have revised the implementation to preserve ordinary unquoted names.

The completion now follows PostgreSQL's conservative quote_ident behavior, using the exact catalog spelling:

Table creation Catalog name Inserted table reference
CREATE TABLE user_authority (...) user_authority user_authority
CREATE TABLE USER_AUTHORITY (...) user_authority user_authority
CREATE TABLE "UserAuthority" (...) UserAuthority "UserAuthority"
CREATE TABLE "USER_AUTHORITY" (...) USER_AUTHORITY "USER_AUTHORITY"

For example, ordinary schema qualification now inserts authschema.user_authority, while mixed case inserts authschema."UserAuthority". An uppercase typed prefix still finds the ordinary lowercase catalog name. This also preserves the distinct quoted-uppercase table discussed above.

Verified locally on native PostgreSQL 18.4: 45 queries generated by the revised completion provider across public, authschema, and "AuthSchema" returned the correct marker rows, including colliding lowercase/quoted-uppercase names; 9 unquoted case-folding controls passed. The formatter matched native quote_ident for all 494 server keywords and 13 additional names. I also accepted and executed seven suggestions through real Monaco in an isolated browser fixture, including lowercase and uppercase input, mixed case, schema quoting, keywords, and the schema-dot trigger.

The focused regressions pass (43/43); the complete bun run test command passes 15,083 tests with zero failures, including all 35 component groups. Static checks, production/library builds, and package type resolution pass. Full application E2E and repository-wide coverage remain for CI; the local browser check used the actual provider and Monaco with a catalog-backed fixture.

Pushed as 1a63657a; the PR description now reflects the revised behavior and these results.

@cevheri

cevheri commented Sep 11, 2026

Copy link
Copy Markdown
Member

We can discuss this;
just my suggestion is that double quotation marks are not a favored feature in SQL editors/and can be risky

so they should only be used for table names containing UPPPERlowercase letters("UserAuthority"), special characters, spaces, etc.("User Authority", "user's authority" etc...)

@mikevillari

Copy link
Copy Markdown
Contributor Author

Thanks @cevheri — I agree that ordinary table names should stay unquoted. The latest revision (1a63657a) already makes that change. For your examples, it inserts:

SELECT * FROM authschema.user_authority;
SELECT * FROM authschema."UserAuthority";
SELECT * FROM "User Authority";
SELECT * FROM "user's authority";

The uppercase case needs one distinction: a table created with unquoted CREATE TABLE USER_AUTHORITY (...) is stored as user_authority, so its completion stays unquoted too. A table explicitly created as "USER_AUTHORITY" has a different, case-sensitive name and needs those quotes to reach the correct table. PostgreSQL documents this distinction here.

I agree that quoting the wrong spelling could change which table a query refers to. This implementation uses the exact name returned by the database, rather than turning the user's typed uppercase text into a quoted name. It also handles names that are SQL keywords, such as "select".

One detail to agree on: it follows PostgreSQL's conservative quote_ident rule, which can still add optional quotes in some edge cases. Is there a particular name the latest revision still quotes that you would prefer left unquoted? A concrete example would help me match the editor behavior you want.

@cevheri

cevheri commented Sep 11, 2026

Copy link
Copy Markdown
Member

how did you handle the code-completion functionality in other SQL databases?

@mikevillari

Copy link
Copy Markdown
Contributor Author

I checked the shared completion code in response to your question. The table-name quoting was already PostgreSQL-only, but the new quoted-name column lookup also affected other dialects. I have now restricted that lookup to PostgreSQL too.

I compared the complete suggestions with the code before this PR: all 544 checks across the other database settings matched, including inserted text and replacement ranges. I also added regression tests for those settings and for switching between PostgreSQL and MySQL, SQLite, DuckDB, SQL Server, and Oracle.

For live checks, I used the real SQLite and DuckDB providers: 28 completion-generated queries returned the expected rows. I have not run live MySQL, SQL Server, or Oracle checks, so the tests for those are editor-level tests only.

The aim is to fix PostgreSQL quoting while preserving the other databases' existing behavior. Their existing special-name completion limitations remain outside this fix; I recorded those in the PR description. Pushed as 3a0d64b1.

@cevheri

cevheri commented Sep 12, 2026

Copy link
Copy Markdown
Member

I’m working on PR #811 right now, which includes some big changes to the schema/objects explorer(nosql databases test remaining)

After that, I’ll do a detailed test of all SQL database providers.

And thanks again for the clean and disciplined work. It’s really appreciated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants