1616 * modified objects routed through the granularity pipeline
1717 * (`restructureChanges`), so names come from the naming spec and `requires`
1818 * from the statement graph — the same derivation every other projection uses.
19+ * Every emitted change carries a full deploy/revert/verify triple: drops are
20+ * `@pgsql/scripts`' `revertFor` inverses of the object's own deploy
21+ * statements (never hand-templated), reverts recreate what was dropped, and
22+ * verifies come from `verifyFor`.
1923 *
2024 * Requires `loadModule()` from `plpgsql-parser` before use.
2125 */
2226import { pathFor , PathStyle } from '@pgpmjs/naming-spec' ;
27+ import { revertFor , verifyFor } from '@pgsql/scripts' ;
2328import type { ObjectIdentity , StatementFacts } from '@pgsql/transform' ;
2429import {
2530 buildStatementGraph ,
@@ -53,6 +58,12 @@ export interface SemanticDiffOptions {
5358 style ?: PathStyle ;
5459}
5560
61+ /** One emitted delta change: a full deploy/revert/verify triple. */
62+ export interface SemanticDeltaChange extends GranularityChange {
63+ revert : string ;
64+ verify : string ;
65+ }
66+
5667export interface SemanticDiffResult {
5768 /** True when both sides describe the same objects with the same shapes. */
5869 identical : boolean ;
@@ -63,7 +74,7 @@ export interface SemanticDiffResult {
6374 * topological order), then CREATE/ALTER changes named and ordered by the
6475 * granularity pipeline.
6576 */
66- changes : GranularityChange [ ] ;
77+ changes : SemanticDeltaChange [ ] ;
6778 warnings : string [ ] ;
6879}
6980
@@ -229,53 +240,6 @@ function tableShape(unit: ObjectUnit): TableShape {
229240const deparseStmt = ( node : Record < string , unknown > ) : string =>
230241 `${ Deparser . deparse ( node , { pretty : false } ) } ;` ;
231242
232- const nameList = ( parts : string [ ] ) : unknown [ ] =>
233- parts . map ( sval => ( { String : { sval } } ) ) ;
234-
235- const DROP_REMOVE_TYPE : Partial < Record < ObjectIdentity [ 'kind' ] , string > > = {
236- schema : 'OBJECT_SCHEMA' ,
237- table : 'OBJECT_TABLE' ,
238- view : 'OBJECT_VIEW' ,
239- sequence : 'OBJECT_SEQUENCE' ,
240- type : 'OBJECT_TYPE' ,
241- index : 'OBJECT_INDEX' ,
242- trigger : 'OBJECT_TRIGGER' ,
243- policy : 'OBJECT_POLICY'
244- } ;
245-
246- /** Synthesize the DROP statement for an object identity, or null if unsupported. */
247- export function dropStatementFor ( identity : ObjectIdentity ) : string | null {
248- const { kind, schema, name, table } = identity ;
249- if ( kind === 'function' ) {
250- return deparseStmt ( {
251- DropStmt : {
252- objects : [ {
253- ObjectWithArgs : {
254- objname : nameList ( schema ? [ schema , name ] : [ name ] ) ,
255- args_unspecified : true
256- }
257- } ] ,
258- removeType : 'OBJECT_FUNCTION' ,
259- behavior : 'DROP_RESTRICT'
260- }
261- } ) ;
262- }
263- const removeType = DROP_REMOVE_TYPE [ kind ] ;
264- if ( ! removeType ) return null ;
265-
266- const scoped = kind === 'trigger' || kind === 'policy' ;
267- const parts = scoped
268- ? [ ...( schema ? [ schema ] : [ ] ) , table ?? '' , name ]
269- : [ ...( schema ? [ schema ] : [ ] ) , name ] ;
270- return deparseStmt ( {
271- DropStmt : {
272- objects : [ { List : { items : nameList ( parts . filter ( Boolean ) ) } } ] ,
273- removeType,
274- behavior : 'DROP_RESTRICT'
275- }
276- } ) ;
277- }
278-
279243/** Column-level ALTERs between two table shapes. */
280244function diffTable (
281245 from : TableShape ,
@@ -377,9 +341,22 @@ export function diffSchemas(
377341
378342 const objects : SemanticObjectDiff [ ] = [ ] ;
379343 const forwardSql : string [ ] = [ ] ;
380- const modifiedChanges : GranularityChange [ ] = [ ] ;
344+ const modifiedChanges : SemanticDeltaChange [ ] = [ ] ;
381345 const dropUnits : ObjectUnit [ ] = [ ] ;
382346
347+ /** `@pgsql/scripts` inverse of a deploy script (drops in reverse topo order). */
348+ const inverseOf = ( deploy : string , context : string ) : string => {
349+ const { sql, warnings : w } = revertFor ( classifyStatements ( deploy ) ) ;
350+ warnings . push ( ...w . map ( x => `${ context } : ${ x } ` ) ) ;
351+ return sql ;
352+ } ;
353+ /** `@pgsql/scripts` existence checks for a deploy script. */
354+ const verifyOf = ( deploy : string , context : string ) : string => {
355+ const { sql, warnings : w } = verifyFor ( classifyStatements ( deploy ) ) ;
356+ warnings . push ( ...w . map ( x => `${ context } : ${ x } ` ) ) ;
357+ return sql ;
358+ } ;
359+
383360 for ( const key of b . order ) {
384361 const unit = b . units . get ( key ) ! ;
385362 const prior = a . units . get ( key ) ;
@@ -399,25 +376,30 @@ export function diffSchemas(
399376 if ( stmts . length > 0 || columnsTouched ) {
400377 objects . push ( diff ) ;
401378 if ( stmts . length > 0 ) {
379+ // The inverse alteration is the same diff with the sides swapped.
380+ const scratch : SemanticObjectDiff = { identity : unit . identity , path, delta : 'modified' } ;
381+ const deploy = stmts . join ( '\n' ) ;
402382 modifiedChanges . push ( {
403383 name : `${ path } /alter` ,
404384 dependencies : [ ] ,
405- deploy : stmts . join ( '\n' )
385+ deploy,
386+ revert : diffTable ( shapeTo , shapeFrom , scratch , [ ] ) . join ( '\n' ) ,
387+ verify : verifyOf ( deploy , path )
406388 } ) ;
407389 }
408390 }
409391 continue ;
410392 }
411393 if ( unitFingerprint ( prior ) !== unitFingerprint ( unit ) ) {
412394 objects . push ( { identity : unit . identity , path, delta : 'modified' } ) ;
413- const drop = dropStatementFor ( unit . identity ) ;
414- if ( ! drop ) {
415- warnings . push ( `${ path } : no DROP synthesized for kind "${ unit . identity . kind } "; recreate manually` ) ;
416- }
395+ const priorDeploy = prior . texts . join ( '\n' ) ;
396+ const newDeploy = unit . texts . join ( '\n' ) ;
417397 modifiedChanges . push ( {
418398 name : path ,
419399 dependencies : [ ] ,
420- deploy : [ ...( drop ? [ drop ] : [ ] ) , ...unit . texts ] . join ( '\n' )
400+ deploy : [ inverseOf ( priorDeploy , path ) , newDeploy ] . filter ( Boolean ) . join ( '\n' ) ,
401+ revert : [ inverseOf ( newDeploy , path ) , priorDeploy ] . filter ( Boolean ) . join ( '\n' ) ,
402+ verify : verifyOf ( newDeploy , path )
421403 } ) ;
422404 }
423405 }
@@ -433,28 +415,35 @@ export function diffSchemas(
433415 // (dependents before dependencies), one change per object.
434416 const position = new Map ( dropUnits . map ( u => [ u , Math . min ( ...u . statements . map ( s => a . graph . order . indexOf ( s ) ) ) ] ) ) ;
435417 dropUnits . sort ( ( x , y ) => position . get ( y ) ! - position . get ( x ) ! ) ;
436- const dropChanges : GranularityChange [ ] = [ ] ;
418+ const dropChanges : SemanticDeltaChange [ ] = [ ] ;
437419 for ( const unit of dropUnits ) {
438- const drop = dropStatementFor ( unit . identity ) ;
439420 const path = pathFor ( unit . identity , { style } ) ;
440- if ( ! drop ) {
441- warnings . push ( `${ path } : no DROP synthesized for kind "${ unit . identity . kind } "; drop manually` ) ;
421+ const recreate = unit . texts . join ( '\n' ) ;
422+ const drop = inverseOf ( recreate , path ) ;
423+ if ( ! drop . trim ( ) || drop . trim ( ) . startsWith ( '--' ) ) {
424+ warnings . push ( `${ path } : no DROP derivable for kind "${ unit . identity . kind } "; drop manually` ) ;
442425 continue ;
443426 }
444427 dropChanges . push ( {
445428 name : `${ path } /drop` ,
446429 dependencies : dropChanges . length > 0 ? [ dropChanges [ dropChanges . length - 1 ] . name ] : [ ] ,
447- deploy : drop
430+ deploy : drop ,
431+ revert : recreate ,
432+ verify : ''
448433 } ) ;
449434 }
450435
451- let forwardChanges : GranularityChange [ ] = [ ] ;
436+ let forwardChanges : SemanticDeltaChange [ ] = [ ] ;
452437 if ( forwardSql . length > 0 ) {
453438 const { changes, warnings : w } = restructureChanges (
454439 [ { name : 'delta' , dependencies : [ ] , deploy : forwardSql . join ( '\n\n' ) } ] ,
455440 { granularity : options . granularity ?? 'object' }
456441 ) ;
457- forwardChanges = changes ;
442+ forwardChanges = changes . map ( c => ( {
443+ ...c ,
444+ revert : inverseOf ( c . deploy , c . name ) ,
445+ verify : verifyOf ( c . deploy , c . name )
446+ } ) ) ;
458447 warnings . push ( ...w ) ;
459448 }
460449
0 commit comments