Skip to content

feat(transform): SqlProgram — statement-level program AST with dirty-aware verbatim/deparse emission - #1535

Merged
pyramation merged 5 commits into
mainfrom
feat/pgpm-program-ast
Jul 30, 2026
Merged

feat(transform): SqlProgram — statement-level program AST with dirty-aware verbatim/deparse emission#1535
pyramation merged 5 commits into
mainfrom
feat/pgpm-program-ast

Conversation

@pyramation

@pyramation pyramation commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Stage 3 of the AST convergence plan (constructive-planning #1323): the statement layer that closes the gap between PgpmScriptAst.body (opaque string) and parsed PostgreSQL ASTs — the seam every bolted-on transform lives in today — plus the first consumer: pgpm/slice exclusion now runs over it.

New in @pgpmjs/transform (src/program.ts):

interface SqlStatementAst {
  stmt: any;              // tagged statement node, mutable
  facts: StatementFacts;  // classifier output
  span: { start; len };   // byte offsets in source
  raw: string;            // verbatim source slice
  dirty: boolean;         // mutated → must deparse
}
interface SqlProgram { source: string; statements: SqlStatementAst[] }

parseSqlProgram(source): SqlProgram   // one parse, facts + spans + ASTs together
emitSqlProgram(program): string       // dirty ? Deparser.deparse(stmt) : verbatim

The emission invariant is the point: clean statements — and every byte between statements (comments, whitespace) — are copied verbatim; only dirty statements go through the deparser. With zero dirty statements the output is byte-identical to the source (tested), so content-addressed bundle hashes are preserved by construction rather than by care.

Second commit rewires pgpm/slice's excludeSubsystem / stripSubsystemSql / blankScriptSql onto it:

- classifyStatements(sql)              // parse #1 (facts)
- parseSql(sql)                        // parse #2 (locations, opaque targets)
- if (parsed.stmts.length !== facts.length) throw  // index-mismatch guard
+ const program = parseSqlProgram(sql) // one parse: facts + stmt + span + raw together

ExcludeResult gains a program field so strip shares the analysis parse; the "parser saw N / classifier saw M, refusing to slice by index" failure mode is now impossible by construction. Public behavior/output format unchanged — slice 48 tests and pgpm/core apply suites (88, live PG) green.

Uses only published deps (@pgsql/transform 18.7.0, plpgsql-parser); once upstream pgsql-parser#323 (spans on StatementFacts) is published, parseSqlProgram can drop its own span computation.

Link to Devin session: https://app.devin.ai/sessions/025fb88043964fdbb335ac5e39df2478
Requested by: @pyramation

… spans, and dirty-aware verbatim/deparse emission
@pyramation pyramation self-assigned this Jul 30, 2026
@devin-ai-integration

Copy link
Copy Markdown
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, CI, and merge conflict monitoring

@pyramation
pyramation merged commit f67a778 into main Jul 30, 2026
16 checks passed
@pyramation
pyramation deleted the feat/pgpm-program-ast branch July 30, 2026 23:04
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