Skip to content

fix: robustly strip psql meta commands - #4479

Open
ignatremizov wants to merge 2 commits into
sqlc-dev:mainfrom
ignatremizov:fix/strip-psql-meta
Open

fix: robustly strip psql meta commands#4479
ignatremizov wants to merge 2 commits into
sqlc-dev:mainfrom
ignatremizov:fix/strip-psql-meta

Conversation

@ignatremizov

@ignatremizov ignatremizov commented Jun 10, 2026

Copy link
Copy Markdown

Summary

This PR makes PostgreSQL schema preprocessing safer for pg_dump / psql-flavored input without turning sqlc into a psql interpreter.

It preserves valid PostgreSQL SQL, strips non-semantic client commands, warns when ignored commands may affect results, and rejects script semantics that cannot be reproduced safely.

Changes

PostgreSQL schema preprocessing

  • Replace the line-based PostgreSQL filter with a single-pass state machine that recognizes top-level psql meta-commands only outside SQL lexical contexts.
  • Preserve backslashes in valid PostgreSQL SQL, including:
    • regular and escape string literals
    • quoted identifiers
    • dollar-quoted bodies
    • line comments and nested block comments
  • Support the valid \meta ... \\ SQL... separator form while preserving trailing SQL, including glued and one-sided-whitespace forms accepted by supported psql versions.
  • Preserve invalid leading \\ input instead of normalizing it into SQL.
  • Handle Unicode dollar-quote tags, identifier boundaries, CR/LF line endings, and \copy ... from stdin data blocks.

Safe handling by execution mode

  • In parse/codegen paths, strip semantic client commands such as \connect, \copy, \gexec, \i, \ir, \q, \quit, and \r, and surface warnings when ignoring them may change results.
  • In apply/live-database paths, reject commands whose effects sqlc cannot reproduce safely.
  • Reject psql conditionals (\if, \elif, \else, \endif) instead of flattening branches and changing SQL semantics.
  • Treat standard_conforming_strings changes as a best-effort parsing aid and warn when parse/codegen preprocessing must approximate session behavior.
  • Keep apply/live-database paths strict while allowing PostgreSQL to execute ordinary transaction and session SQL directly.

Schema-loading integration

  • Add engine-aware PreprocessSchema() and PreprocessSchemaForApply() entry points.
  • Apply preprocessing consistently in:
    • compiler parsing and generate
    • createdb
    • verify
    • managed vet
    • PostgreSQL test database seeding
  • Propagate non-fatal preprocessing warnings through compiler and command callers.
  • Pass apply-safe schema text to managed analyzers while retaining parse-safe schema text for catalog analysis.

Shared apply-time loading

  • Add schemautil.LoadSchemasForApply() to centralize schema glob expansion, file loading, apply-mode preprocessing, and warning delivery.
  • Reuse the loader across command setup and PostgreSQL test database seeding so apply-time behavior remains consistent.
  • Preserve read-only PostgreSQL fixture caching by hashing the preprocessed DDL.
  • Leave ordinary PostgreSQL errors unchanged; for example, schema DDL that conflicts with an existing database object still fails normally.

Behavioral impact

  • sqlc accepts more pg_dump / psql-flavored schema files without corrupting valid SQL containing backslashes.
  • Parse/codegen paths remain permissive where safe and report ignored client behavior.
  • Apply/live-database paths reject client-side semantics that cannot be reproduced reliably.
  • Arbitrary psql script execution remains intentionally out of scope.

Testing

  • go test ./internal/migrations ./internal/compiler ./internal/schemautil ./internal/cmd ./internal/sqltest/local
  • Regression coverage includes documented and unknown meta-commands, literals, comments, dollar quotes, inline separators, semantic warnings, apply-mode rejections, copy data, line endings, compiler warning propagation, and PostgreSQL apply-time loading.
  • go test ./internal/migrations -coverprofile=/tmp/sqlc-migrations.cover reports 100.0% statement coverage for internal/migrations.

Related

Replaces #4177.

Addresses gbarr's review comment on #4082, which closes #4065.

Co-authored-by: Andrew Benton andrew@sqlc.dev

@ignatremizov

Copy link
Copy Markdown
Author

@kyleconroy FYI

Replace naive PostgreSQL schema preprocessing with a single-pass state machine that distinguishes top-level psql meta-commands from valid SQL backslashes, literals, identifiers, comments, and dollar-quoted bodies.

The previous implementation could leave pg_dump/client backslash directives in schema-loading paths or strip too aggressively, breaking valid SQL containing:
- Backslashes in string literals, including `E'...'` escapes and simple `standard_conforming_strings` variants
- Meta-command text in comments or documentation
- Dollar-quoted function bodies, including Unicode-tagged bodies
- Double-quoted identifiers and identifiers containing `$`

Changes:
- Add engine-aware `PreprocessSchema()` and `PreprocessSchemaForApply()` helpers so rollback removal always applies while PostgreSQL psql stripping is mode-aware.
- Replace line-based PostgreSQL filtering with a single-pass lexer that tracks single quotes, double quotes, dollar quotes, line comments, nested block comments, and statement boundaries.
- Handle escape-string prefixes, simple `standard_conforming_strings` changes, Unicode dollar-quote tags, identifier-boundary checks, documented psql meta-commands, and broader unknown top-level backslash directives.
- Preserve SQL after a valid inline `\\` separator that follows a meta-command, including glued and one-sided-whitespace forms observed in psql 13.22 / 14.19 / 15.14 / 16.10 / 17.6 / 17.10; preserve invalid leading `\\` input instead of normalizing it into SQL.
- Strip semantic psql commands such as `\connect`, includes, `\copy`, `\gexec`, `\q`, `\quit`, and `\r` with warnings in parse/codegen paths, but reject them in schema-application paths where sqlc cannot reproduce their effects safely.
- Reject psql conditionals (`\if`, `\elif`, `\else`, `\endif`) instead of flattening branches and changing SQL semantics.
- Remove `\copy ... from stdin` payload rows through an exact `\.` terminator in parse mode, and reject unterminated copy data.
- Treat `standard_conforming_strings` and transaction-scoped script behavior as best-effort parsing aids rather than full psql emulation; report approximation warnings in parse mode while suppressing that parse-only warning for live apply mode.
- Wire preprocessing and warning propagation into compiler parsing, generate processing, `createdb`, `verify`, managed `vet`, and PostgreSQL sqltest seeding paths.
- Add regression coverage for documented meta-commands, unknown directives, literals, comments, dollar quotes, inline separators, semantic warnings, apply-mode rejections, copy data, line endings, and managed/PostgreSQL preprocessing rollout.

Performance improvements:
- Pre-allocate output buffers with `strings.Builder.Grow()`.
- Keep parsing single-pass rather than rescanning line slices.
- Reuse engine-aware preprocessing helpers across schema-loading paths.

Testing:
- `go test ./internal/migrations ./internal/compiler ./internal/schemautil ./internal/cmd ./internal/sqltest/...`
Extract the repeated apply-time schema glob/read/preprocess/warn loop into a shared helper so command setup and PostgreSQL sqltest seeding use the same schema preprocessing path.

Changes:
- Add `schemautil.LoadSchemasForApply()` to expand schema globs, read files, run `PreprocessSchemaForApply()`, and surface warnings through a caller-provided callback.
- Update `createdb`, `verify`, and managed `vet` setup to reuse the shared loader instead of open-coding the same apply-time preprocessing loop.
- Update PostgreSQL sqltest seeding helpers to reuse the shared loader for both regular and read-only database setup.
- Preserve read-only PostgreSQL fixture cache behavior by hashing the preprocessed DDL returned by the shared loader.
- Leave schema semantics unchanged: unsafe psql meta-commands are still rejected by apply-mode preprocessing, and invalid SQL continues to fail with the database's normal PostgreSQL error.

Behavioral effect:
- Apply-time callers and PostgreSQL test helpers now share one codepath, reducing drift between managed-database setup and sqltest seeding.
- No special-case rewriting of pg_dump `public` schema DDL is performed; callers still see PostgreSQL's native apply-time behavior.

Testing:
- `go test ./internal/migrations ./internal/compiler ./internal/schemautil ./internal/cmd ./internal/sqltest/...`
@ignatremizov

Copy link
Copy Markdown
Author

@kyleconroy rebased onto the latest main again. Is there anything else needed before this can be merged?

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.

SQLC fails for psql meta-commands like \restrict

1 participant