Skip to content

fix(deparser): support ALTER STATISTICS RENAME and quote/validate operator names - #370

Open
pyramation wants to merge 1 commit into
mainfrom
devin/1792901466-deparser-statistics-operators
Open

pyramation wants to merge 1 commit into
mainfrom
devin/1792901466-deparser-statistics-operators

Conversation

@pyramation

Copy link
Copy Markdown
Collaborator

Summary

ALTER STATISTICS

  • RenameStmt with OBJECT_STATISTIC_EXT threw Unsupported RenameStmt renameType. It now emits ALTER STATISTICS [schema.]name RENAME TO new, with the qualified name quoted.
  • AlterStatsStmt dropped IF EXISTS (missing_ok), emitted invalid SET STATISTICS with no value for SET STATISTICS DEFAULT (the PG18 AST has no stxstattarget), and did not quote names.

Operator names. The schema part of a qualified operator was never quoted, and the operator symbol was emitted unchecked. Before → after:

input before after
a OPERATOR("My Schema".+) b OPERATOR(My Schema.+) (syntax error) unchanged
a OPERATOR("S".=) ANY (...) OPERATOR(S.=) (a different schema) unchanged
CREATE OPERATOR ... COMMUTATOR = OPERATOR("My Schema".===) Commutator = "My Schema", "===" Commutator = OPERATOR("My Schema".===)
ALTER OPERATOR ... SET (COMMUTATOR = OPERATOR("My Schema".===)) commutator = 'My Schema, ===' commutator = OPERATOR("My Schema".===)
COMMENT ON OPERATOR "My Schema".=== (int, int) COMMENT ON OPERATOR My Schema(int, int) unchanged
opclass/opfamily OPERATOR 1 "S".< (int, int) S.<(int, int) "S".<(int, int)
EXCLUDE (c WITH OPERATOR("S".&&)) c WITH S (syntax error) c WITH OPERATOR("S".&&)

All of these now go through one helper, qualifiedOperatorName(names): it quotes each schema part with QuoteUtils.quoteIdentifier and checks the symbol. deparseOperatorName wraps qualified names in OPERATOR(...). ObjectWithArgs takes an isOperator flag, which defaults to context.objtype === 'OBJECT_OPERATOR' and is passed by AlterOperatorStmt and opclass operator items.

The symbol check throws Invalid operator name for any name that would not lex back as the same single operator token:

  • characters outside ~!@#^&|?+-*/%<>=`
  • names containing -- or /*
  • !=, which the lexer rewrites to <>
  • =>
  • 64 or more characters
  • multi-character names ending in +/- that don't contain one of ~!@#^&|?%, e.g. +-, which lexes as + -`

Before this change those names were emitted as-is, so the SQL silently reparsed as a different expression.

Tests

  • New kitchen-sink fixtures misc/alter-statistics.sql and misc/operator-names.sql, regenerated with npm run kitchen-sink.
  • __tests__/misc/operator-names.test.ts uses hand-built ASTs, because the parser can never produce an invalid operator name. It covers the rejected names and some valid edge cases (~-, @-@, a quoted schema).
  • Before the fix (old src/deparser.ts checked out): both fixture tests and 10/13 of the hand-built cases failed, with Unsupported RenameStmt renameType: OBJECT_STATISTIC_EXT, syntax error at or near "Schema", or Received function did not throw. After the fix all 15 pass.
  • Full suites: deparser 763/763, transform 250/250, plpgsql-deparser 115/115, transform-ast 1443/1443. tsc is clean and lint reports 0 errors.

This is independent of #368 and #369.

Link to Devin session: https://app.devin.ai/sessions/cb07c6509db64478af655f52611468f4
Open in Devin Desktop: https://app.devin.ai/desktop/session/cb07c6509db64478af655f52611468f4?variant=devin
Requested by: @pyramation

@devin-ai-integration

Copy link
Copy Markdown
Contributor

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

  • Disable automatic comment, CI, and merge conflict monitoring

@tenki-reviewer

tenki-reviewer Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

Review complete. 🟡 1 medium

💬 Inline comments (1)


The change teaches the deparser to emit qualified operator references (OPERATOR(schema.op)) and validate operator symbol names against PostgreSQL lexer rules, and extends RenameStmt/AlterStatsStmt handling for statistics objects including IF EXISTS and SET STATISTICS DEFAULT.

Files Change
packages/deparser/src/deparser.ts Adds isValidOperatorSymbol/qualifiedOperatorName/deparseOperatorName helpers, wires operator-name lists through A_Expr, CommentStmt, DefElem commutator/negator, DefineStmt, and ObjectWithArgs; adds OBJECT_STATISTIC_EXT and IF EXISTS/DEFAULT handling for ALTER STATISTICS.
__fixtures__/kitchen-sink/misc/{operator-names,alter-statistics}.sql, __fixtures__/generated/generated.json New fixtures covering 18 operator-name cases and 9 ALTER STATISTICS cases.
packages/deparser/__tests__/* Kitchen-sink tests for the two new fixture sets plus a unit test for operator-name validation.

Reviewed commit: e2c6d77

@tenki-reviewer tenki-reviewer Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR adds operator-name validation and qualified OPERATOR(schema.op) deparsing plus ALTER STATISTICS support to the deparser, backed by new kitchen-sink fixtures and tests.

Key findings

// The lexer rewrites `!=` to `<>` and treats `=>` as its own token.
if (op === '!=' || op === '=>') return false;
// A multi-char operator may only end in + or - if it contains one of these.
return op.length === 1 || !/[+-]$/.test(op) || /[~!@#^&|`?%]/.test(op);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 bug · medium

% wrongly accepted in operators ending in +/-

isValidOperatorSymbol includes % in the exception class /[~!@#^&|?%]/that legalizes multi-character operators ending in+or-(packages/deparser/src/deparser.ts:991). PostgreSQL's lexer only allows such operators when they contain at least one of~ ! @ # ^ & | ` ?—%does not qualify. A name like%+` therefore passes validation and is deparsed, but the emitted SQL is rejected by the real PostgreSQL parser, breaking the round-trip guarantee this check was added to enforce.

📋 Prompt for AI Agents

In packages/deparser/src/deparser.ts at line 991, change the trailing +/- exception regex in isValidOperatorSymbol from /[~!@#^&|?%]/to/[~!@#^&|?]/. PostgreSQL's lexer only allows a multi-character operator ending in + or - when it contains one of ~ ! @ # ^ & | ? — % does not qualify, so the current code accepts invalid operators like%+` that PostgreSQL will reject on reparse.

@devin-ai-integration

Copy link
Copy Markdown
Contributor

Folded into #371

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.

1 participant