@@ -161,9 +161,7 @@ describe('transition tests', () => {
161161 age : integer ( 'age' ) ,
162162 test : integer ( 'test' ) ,
163163 } ,
164- (
165- t ,
166- ) => [
164+ ( t ) => [
167165 check ( 'age_check' , sql `${ t . age } > 18` ) ,
168166 index ( 'users_name_idx' ) . on ( t . name ) ,
169167 unique ( ) . on ( t . test ) ,
@@ -189,9 +187,7 @@ describe('transition tests', () => {
189187 age : integer ( 'age' ) ,
190188 test : integer ( 'test' ) ,
191189 } ,
192- (
193- t ,
194- ) => [
190+ ( t ) => [
195191 check ( 'age_check' , sql `${ t . age } > 18` ) ,
196192 index ( 'users_name_idx' ) . on ( t . name ) ,
197193 unique ( ) . on ( t . test ) ,
@@ -278,9 +274,7 @@ describe('transition tests', () => {
278274 age : integer ( 'age' ) ,
279275 test : integer ( 'test' ) ,
280276 } ,
281- (
282- t ,
283- ) => [
277+ ( t ) => [
284278 check ( 'age_check' , sql `${ t . age } > 18` ) ,
285279 index ( 'users_name_idx' ) . on ( t . name ) ,
286280 unique ( ) . on ( t . test ) ,
@@ -307,9 +301,7 @@ describe('transition tests', () => {
307301 age : integer ( 'age' ) ,
308302 test : integer ( 'test' ) ,
309303 } ,
310- (
311- t ,
312- ) => [
304+ ( t ) => [
313305 check ( 'age_check' , sql `${ t . age } > 18` ) ,
314306 index ( 'users_name_idx' ) . on ( t . name ) ,
315307 unique ( ) . on ( t . test ) ,
@@ -362,9 +354,7 @@ describe('transition tests', () => {
362354 age : integer ( 'age' ) ,
363355 test : integer ( 'test' ) ,
364356 } ,
365- (
366- t ,
367- ) => [
357+ ( t ) => [
368358 check ( 'age_check' , sql `${ t . age } > 18` ) ,
369359 index ( 'users_name_idx' ) . on ( t . name ) ,
370360 unique ( ) . on ( t . test ) ,
@@ -391,9 +381,7 @@ describe('transition tests', () => {
391381 age : integer ( 'age' ) ,
392382 test : integer ( 'test' ) ,
393383 } ,
394- (
395- t ,
396- ) => [
384+ ( t ) => [
397385 check ( 'age_check' , sql `${ t . age } > 18` ) ,
398386 index ( 'users_name_idx' ) . on ( t . name ) ,
399387 unique ( ) . on ( t . test ) ,
@@ -411,7 +399,10 @@ describe('transition tests', () => {
411399 usersSubs : usersSubsTo ,
412400 } ;
413401
414- const expectedTypes : JsonStatement [ 'type' ] [ ] = [ 'rename_table' , 'move_table' ] ;
402+ const expectedTypes : JsonStatement [ 'type' ] [ ] = [
403+ 'rename_table' ,
404+ 'move_table' ,
405+ ] ;
415406
416407 const { afterPatch, fromTs, statements } = await applyTransition ( {
417408 from,
@@ -570,7 +561,9 @@ describe('transition tests', () => {
570561 } ) ;
571562
572563 test ( 'recreate_column' , async ( ) => {
573- const from = { users : pgTable ( 'users' , { id : integer ( 'id' ) . generatedAlwaysAs ( sql `13` ) } ) } ;
564+ const from = {
565+ users : pgTable ( 'users' , { id : integer ( 'id' ) . generatedAlwaysAs ( sql `13` ) } ) ,
566+ } ;
574567 const to = {
575568 users : pgTable ( 'users' , { id : integer ( 'id' ) . generatedAlwaysAs ( sql `15` ) } ) ,
576569 } ;
@@ -945,7 +938,11 @@ describe('transition tests', () => {
945938 } ) ,
946939 } ;
947940
948- const expectedTypes : JsonStatement [ 'type' ] [ ] = [ 'alter_column' , 'alter_column' , 'drop_pk' ] ;
941+ const expectedTypes : JsonStatement [ 'type' ] [ ] = [
942+ 'alter_column' ,
943+ 'alter_column' ,
944+ 'drop_pk' ,
945+ ] ;
949946
950947 const { afterPatch, fromTs, statements } = await applyTransition ( {
951948 from,
@@ -1788,7 +1785,9 @@ describe('transition tests', () => {
17881785 const from = { users } ;
17891786 const to = {
17901787 users,
1791- policy : pgPolicy ( 'users_policy' , { for : 'select' , to : 'public' } ) . link ( users ) ,
1788+ policy : pgPolicy ( 'users_policy' , { for : 'select' , to : 'public' } ) . link (
1789+ users ,
1790+ ) ,
17921791 } ;
17931792
17941793 const expectedTypes : JsonStatement [ 'type' ] [ ] = [ 'create_policy' ] ;
@@ -1815,7 +1814,9 @@ describe('transition tests', () => {
18151814
18161815 const from = {
18171816 users,
1818- policy : pgPolicy ( 'users_policy' , { for : 'select' , to : 'public' } ) . link ( users ) ,
1817+ policy : pgPolicy ( 'users_policy' , { for : 'select' , to : 'public' } ) . link (
1818+ users ,
1819+ ) ,
18191820 } ;
18201821 const to = { users } ;
18211822
@@ -1843,11 +1844,16 @@ describe('transition tests', () => {
18431844
18441845 const from = {
18451846 users,
1846- policy : pgPolicy ( 'users_policy' , { for : 'select' , to : 'public' } ) . link ( users ) ,
1847+ policy : pgPolicy ( 'users_policy' , { for : 'select' , to : 'public' } ) . link (
1848+ users ,
1849+ ) ,
18471850 } ;
18481851 const to = {
18491852 users,
1850- policy : pgPolicy ( 'users_policy_renamed' , { for : 'select' , to : 'public' } ) . link ( users ) ,
1853+ policy : pgPolicy ( 'users_policy_renamed' , {
1854+ for : 'select' ,
1855+ to : 'public' ,
1856+ } ) . link ( users ) ,
18511857 } ;
18521858
18531859 const expectedTypes : JsonStatement [ 'type' ] [ ] = [ 'rename_policy' ] ;
@@ -1874,11 +1880,19 @@ describe('transition tests', () => {
18741880
18751881 const from = {
18761882 users,
1877- policy : pgPolicy ( 'users_policy' , { for : 'select' , to : 'public' , using : sql `` } ) . link ( users ) ,
1883+ policy : pgPolicy ( 'users_policy' , {
1884+ for : 'select' ,
1885+ to : 'public' ,
1886+ using : sql `` ,
1887+ } ) . link ( users ) ,
18781888 } ;
18791889 const to = {
18801890 users,
1881- policy : pgPolicy ( 'users_policy' , { for : 'select' , to : 'public' , using : sql `1` } ) . link ( users ) ,
1891+ policy : pgPolicy ( 'users_policy' , {
1892+ for : 'select' ,
1893+ to : 'public' ,
1894+ using : sql `1` ,
1895+ } ) . link ( users ) ,
18821896 } ;
18831897
18841898 const expectedTypes : JsonStatement [ 'type' ] [ ] = [ 'alter_policy' ] ;
@@ -1905,7 +1919,9 @@ describe('transition tests', () => {
19051919
19061920 const from = {
19071921 users,
1908- policy : pgPolicy ( 'users_policy' , { for : 'select' , to : 'public' } ) . link ( users ) ,
1922+ policy : pgPolicy ( 'users_policy' , { for : 'select' , to : 'public' } ) . link (
1923+ users ,
1924+ ) ,
19091925 } ;
19101926 const to = {
19111927 users,
@@ -2190,6 +2206,107 @@ describe('transition tests', () => {
21902206 expect ( afterPatch ) . toStrictEqual ( fromTs ) ;
21912207 } ) ;
21922208
2209+ test ( 'merge commutative branches with alter_enum (no duplicates)' , async ( ) => {
2210+ // Simulates 3 leaf branches forking from the same parent, each independently
2211+ // modifying the same enum. When merging, all their alter_enum statements are
2212+ // concatenated and applied to the parent snapshot. Previously this caused
2213+ // duplicate enum entries because the 2nd/3rd alter_enum's `from` didn't match
2214+ // the already-modified enum.
2215+ const parentEnum = pgEnum ( 'status' , [ 'a' , 'b' ] ) ;
2216+ const finalEnum = pgEnum ( 'status' , [ 'a' , 'b' , 'c' , 'd' , 'e' ] ) ;
2217+
2218+ const parentSchema = {
2219+ statusEnum : parentEnum ,
2220+ items : pgTable ( 'items' , {
2221+ id : integer ( 'id' ) ,
2222+ status : parentEnum ( 'status' ) ,
2223+ } ) ,
2224+ } ;
2225+
2226+ // Branch A: adds 'c'
2227+ const branchAEnum = pgEnum ( 'status' , [ 'a' , 'b' , 'c' ] ) ;
2228+ const branchA = {
2229+ statusEnum : branchAEnum ,
2230+ items : pgTable ( 'items' , {
2231+ id : integer ( 'id' ) ,
2232+ status : branchAEnum ( 'status' ) ,
2233+ } ) ,
2234+ } ;
2235+
2236+ // Branch B: adds 'd', 'e'
2237+ const branchBEnum = pgEnum ( 'status' , [ 'a' , 'b' , 'd' , 'e' ] ) ;
2238+ const branchB = {
2239+ statusEnum : branchBEnum ,
2240+ items : pgTable ( 'items' , {
2241+ id : integer ( 'id' ) ,
2242+ status : branchBEnum ( 'status' ) ,
2243+ } ) ,
2244+ } ;
2245+
2246+ // Get statements from each branch (parent -> branch)
2247+ const { statements : statementsA } = await diff ( parentSchema , branchA , [ ] ) ;
2248+ const { statements : statementsB } = await diff ( parentSchema , branchB , [ ] ) ;
2249+
2250+ // Combine all statements (simulates flatMap of leaf statements)
2251+ const combinedStatements = [ ...statementsA , ...statementsB ] ;
2252+
2253+ const base : PostgresSnapshot = {
2254+ version : '8' ,
2255+ dialect : 'postgres' ,
2256+ id : 'snapshot-id' ,
2257+ prevIds : [ originId ] ,
2258+ ddl : drizzleToDDL ( parentSchema ) . ddl . entities . list ( ) ,
2259+ renames : [ ] ,
2260+ } ;
2261+
2262+ const actual = generateLatestSnapshot ( base , combinedStatements ) ;
2263+
2264+ // The expected snapshot is what the final schema looks like
2265+ const expected = {
2266+ ...base ,
2267+ ddl : drizzleToDDL ( {
2268+ statusEnum : finalEnum ,
2269+ items : pgTable ( 'items' , {
2270+ id : integer ( 'id' ) ,
2271+ status : finalEnum ( 'status' ) ,
2272+ } ) ,
2273+ } ) . ddl . entities . list ( ) ,
2274+ } ;
2275+
2276+ const stable = ( value : unknown ) : unknown => {
2277+ if ( Array . isArray ( value ) ) {
2278+ return value . map ( stable ) ;
2279+ }
2280+ if ( value && typeof value === 'object' ) {
2281+ return Object . fromEntries (
2282+ Object . entries ( value as Record < string , unknown > )
2283+ . sort ( ( [ left ] , [ right ] ) => left . localeCompare ( right ) )
2284+ . map ( ( [ key , entry ] ) => [ key , stable ( entry ) ] ) ,
2285+ ) ;
2286+ }
2287+ return value ;
2288+ } ;
2289+
2290+ const ddlEntries = ( ddl : PostgresEntity [ ] ) =>
2291+ ddl
2292+ . map ( ( entry ) => JSON . stringify ( stable ( entry ) ) )
2293+ . sort ( ( left , right ) => left . localeCompare ( right ) ) ;
2294+
2295+ const actualDdl = ddlEntries ( actual . ddl ) ;
2296+ const expectedDdl = ddlEntries ( expected . ddl ) ;
2297+
2298+ // Verify no duplicate enum entries
2299+ const enumEntries = actual . ddl . filter (
2300+ ( e ) => ( e as any ) . entityType === 'enums' ,
2301+ ) ;
2302+ expect (
2303+ enumEntries . length ,
2304+ `Expected exactly 1 enum entry, but found ${ enumEntries . length } . Duplicate enums were created during branch merge.` ,
2305+ ) . toBe ( 1 ) ;
2306+
2307+ expect ( actualDdl ) . toStrictEqual ( expectedDdl ) ;
2308+ } ) ;
2309+
21932310 test ( 'alter view' , async ( ) => {
21942311 const from = {
21952312 users : pgTable ( 'users' , { id : integer ( 'id' ) } ) ,
@@ -2202,7 +2319,9 @@ describe('transition tests', () => {
22022319 active_users : pgView ( 'active_users' , {
22032320 id : integer ( 'id' ) ,
22042321 name : varchar ( 'name' ) ,
2205- } ) . with ( { checkOption : 'local' } ) . as ( sql `SELECT id, name FROM users WHERE active = true` ) ,
2322+ } )
2323+ . with ( { checkOption : 'local' } )
2324+ . as ( sql `SELECT id, name FROM users WHERE active = true` ) ,
22062325 } ;
22072326
22082327 const expectedTypes : JsonStatement [ 'type' ] [ ] = [ 'alter_view' ] ;
0 commit comments