@@ -72,16 +72,30 @@ function qualifiedName(raw: string): string {
7272/**
7373 * The statement with string literals and comments blanked, so only executable SQL is matched.
7474 * An escape string (`E'…'`) is read with its backslash escapes, so `\'` does not end it early.
75- * A dollar-quoted body stays only where it runs, after `DO` or a function's `AS`, with its own
76- * literals blanked in turn; dollar-quoted data is blanked like any other string.
75+ * A dollar-quoted body stays only where it runs, after `DO` or a function's `AS`, and a literal
76+ * stays where `EXECUTE` runs it as dynamic SQL, each with its own literals blanked in turn. Every
77+ * other literal is data. Dynamic SQL assembled by concatenation or `format()` is not followed.
7778 */
7879function executableSql ( sql : string ) : string {
7980 return sql . replace (
80- / \$ ( \w * ) \$ ( [ \s \S ] * ?) \$ \1\$ | (?< ! [ \w $ ] ) [ E e ] ' (?: [ ^ ' \\ ] | \\ [ \s \S ] | ' ' ) * ' | ' (?: [ ^ ' ] | ' ' ) * ' | - - [ ^ \n ] * | \/ \* [ \s \S ] * ?\* \/ / g,
81- ( _match , tag : string | undefined , body : string | undefined , offset : number ) => {
82- if ( body === undefined ) return ' '
83- const runs = / \b (?: D O | A S ) \s * $ | \b D O \s + L A N G U A G E \s + \w + \s * $ / i. test ( sql . slice ( 0 , offset ) )
84- return runs ? `$${ tag } $${ executableSql ( body ) } $${ tag } $` : ' '
81+ / \$ ( \w * ) \$ ( [ \s \S ] * ?) \$ \1\$ | (?< ! [ \w $ ] ) [ E e ] ' ( (?: [ ^ ' \\ ] | \\ [ \s \S ] | ' ' ) * ) ' | ' ( (?: [ ^ ' ] | ' ' ) * ) ' | - - [ ^ \n ] * | \/ \* [ \s \S ] * ?\* \/ / g,
82+ (
83+ _match : string ,
84+ tag : string | undefined ,
85+ body : string | undefined ,
86+ escaped : string | undefined ,
87+ plain : string | undefined ,
88+ offset : number
89+ ) => {
90+ const before = sql . slice ( 0 , offset )
91+ if ( body !== undefined ) {
92+ const runs = / \b (?: D O | A S ) \s * $ | \b D O \s + L A N G U A G E \s + \w + \s * $ / i. test ( before )
93+ return runs ? `$${ tag } $${ executableSql ( body ) } $${ tag } $` : ' '
94+ }
95+ if ( ! / \b E X E C U T E \s * $ / i. test ( before ) ) return ' '
96+ if ( escaped !== undefined ) return ` ${ executableSql ( escaped . replace ( / \\ ( .) / g, '$1' ) ) } `
97+ if ( plain !== undefined ) return ` ${ executableSql ( plain . replace ( / ' ' / g, "'" ) ) } `
98+ return ' '
8599 }
86100 )
87101}
@@ -254,10 +268,12 @@ interface RawMatch {
254268 * migration, by schema-qualified name so a same-named table in another schema
255269 * is not mistaken for one — ops against a brand-new table have no old rows and no live
256270 * traffic, so they are always safe and skipped. `sawCommit` tracks whether a
257- * `COMMIT;` breakpoint preceded a CONCURRENTLY index (see migrate.ts).
271+ * `COMMIT;` breakpoint preceded a CONCURRENTLY index (see migrate.ts). Only
272+ * executable SQL is classified, so words in literals, comments and dollar-quoted
273+ * data never count as operations.
258274 */
259275function classify ( sql : string , createdTables : Set < string > , sawCommit : boolean ) : RawMatch [ ] {
260- const s = sql . replace ( / \s + / g, ' ' ) . trim ( )
276+ const s = executableSql ( sql ) . replace ( / \s + / g, ' ' ) . trim ( )
261277 const matches : RawMatch [ ] = [ ]
262278
263279 const alterTable = s . match (
0 commit comments