Skip to content

Conversation

@pyramation
Copy link
Collaborator

@pyramation pyramation commented Dec 25, 2025

feat: add relaxed quoting for qualified name tails

Summary

This PR reduces unnecessary quoting of identifiers that appear after a dot in qualified names (e.g., schema.name, table.column).

PostgreSQL's grammar accepts all keyword categories—including RESERVED_KEYWORD like select, from, table—in qualified name positions. The previous implementation was over-conservative, quoting keywords like float, interval, boolean even when they appeared after a dot where quoting isn't required.

Before: faker."float", faker."interval", pg_catalog."substring"
After: faker.float, faker.interval, pg_catalog.substring

Changes:

  • Added quoteIdentifierAfterDot() method that only quotes for lexical reasons (uppercase, special chars, leading digits) not keyword reasons
  • Added quoteDottedName(parts[]) helper that applies strict quoting to the first part and relaxed quoting to subsequent parts
  • Updated FuncCall and CreateFunctionStmt to use quoteDottedName() for function names
  • Updated RangeVar and ResTarget indirection handling to use quoteIdentifierAfterDot()
  • Updated quoteQualifiedIdentifier() to use relaxed quoting for the tail component
  • Removed unused nquotes variable from quoteIdentifier()
  • Updated 14 snapshots to reflect the new unquoted output
  • Added comprehensive QUOTING-RULES.md documentation explaining the quoting strategy, PostgreSQL background, algorithms, and contributor guidance

Review & Testing Checklist for Human

  • Verify the grammar claim: Test that CREATE FUNCTION faker.float() RETURNS float AS $$ SELECT 1.0; $$ LANGUAGE sql and similar statements actually parse in PostgreSQL without quotes
  • Check for missed call sites: Search for other patterns in deparser.ts that build dotted names (e.g., .join('.') patterns) that might need updating to use quoteDottedName()
  • Verify FuncCall comparisons still work: The code compares name === 'pg_catalog.substring' etc. after quoting - verify these comparisons still match since the function names should remain unquoted
  • Review QUOTING-RULES.md: Ensure the documentation accurately describes the strategy and is helpful for future contributors

Recommended test plan: Parse and deparse SQL like CREATE FUNCTION faker.float() RETURNS float AS $$ SELECT 1.0; $$ LANGUAGE sql and verify the output is valid PostgreSQL that can be re-parsed.

Notes

  • Pre-existing test failures in the repo (tables-13.sql, tables-14.sql) are unrelated to this change - they involve case sensitivity issues with type names like AlertLevel vs alertlevel
  • The quoteIdentifier() method remains unchanged as the strict PostgreSQL-faithful port for standalone identifiers
  • The new QUOTING-RULES.md documentation covers: PostgreSQL identifier folding, keyword categories, the strict vs relaxed quoting algorithms, composition helpers, deparser integration rules, and common pitfalls

Link to Devin run: https://app.devin.ai/sessions/019c4a70e9164830aef1f807aad33e1d
Requested by: Dan Lynch (@pyramation)

PostgreSQL's grammar accepts all keyword categories (including RESERVED_KEYWORD)
in qualified name positions (after a dot). This change adds a new
quoteIdentifierQualifiedTail() method that only quotes for lexical reasons
(uppercase, special characters, leading digits) not for keyword reasons.

This allows the deparser to emit:
- faker.float instead of faker."float"
- myschema.select instead of myschema."select"
- t.from instead of t."from"

while still correctly quoting unqualified identifiers that are keywords.

Empirically verified with libpg-query that all keyword categories parse
successfully in qualified positions across DDL and DML contexts.
@devin-ai-integration
Copy link
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

- Rename quoteIdentifierQualifiedTail to quoteIdentifierAfterDot for clarity
- Add quoteDottedName() helper that applies strict quoting to first part
  and relaxed (lexical-only) quoting to subsequent parts
- Update FuncCall and CreateFunctionStmt to use quoteDottedName()
- Remove unused nquotes variable from quoteIdentifier()
- Update snapshots to show unquoted function names (faker.float, etc.)
Comprehensive documentation of the identifier quoting strategy including:
- PostgreSQL background on identifier folding and quoting rules
- The strict quoting algorithm (quote_identifier port)
- The relaxed after-dot quoting algorithm
- Grammar-slot sensitivity explanation
- Composition helpers (quoteDottedName, quoteQualifiedIdentifier)
- Deparser integration rules and anti-patterns
- Examples and test matrix
- Keyword table accuracy notes
@pyramation pyramation merged commit eb19eb9 into main Dec 25, 2025
14 checks passed
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.

2 participants