@@ -14,6 +14,9 @@ defmodule AshPostgres.MigrationGenerator do
1414migration_path: nil ,
1515tenant_migration_path: nil ,
1616quiet: false ,
17+ current_snapshots: nil ,
18+ answers: [ ] ,
19+ no_shell?: false ,
1720format: true ,
1821dry_run: false ,
1922check_generated: false ,
@@ -43,7 +46,7 @@ defmodule AshPostgres.MigrationGenerator do
4346tenant_snapshots_to_include_in_global =
4447tenant_snapshots
4548|> Enum . filter ( & & 1 . multitenancy . global )
46- |> Enum . map ( & Map . put ( & 1 , :multitenancy , % { strategy: nil , attribute: nil , global: false } ) )
49+ |> Enum . map ( & Map . put ( & 1 , :multitenancy , % { strategy: nil , attribute: nil , global: nil } ) )
4750
4851snapshots = snapshots ++ tenant_snapshots_to_include_in_global
4952
@@ -57,6 +60,48 @@ defmodule AshPostgres.MigrationGenerator do
5760create_migrations ( snapshots , opts , false )
5861end
5962
63+ @ doc """
64+ A work in progress utility for getting snapshots.
65+
66+ Does not support everything supported by the migration generator.
67+ """
68+ def take_snapshots ( api , repo ) do
69+ all_resources = Ash.Api . resources ( api )
70+
71+ all_resources
72+ |> Enum . filter ( & ( Ash.DataLayer . data_layer ( & 1 ) == AshPostgres.DataLayer ) )
73+ |> Enum . filter ( & ( AshPostgres . repo ( & 1 ) == repo ) )
74+ |> Enum . flat_map ( & get_snapshots ( & 1 , all_resources ) )
75+ end
76+
77+ @ doc """
78+ A work in progress utility for getting operations between snapshots.
79+
80+ Does not support everything supported by the migration generator.
81+ """
82+ def get_operations_from_snapshots ( old_snapshots , new_snapshots , opts \\ [ ] ) do
83+ opts = % { opts ( opts ) | no_shell?: true }
84+
85+ old_snapshots = Enum . map ( old_snapshots , & sanitize_snapshot / 1 )
86+
87+ new_snapshots
88+ |> deduplicate_snapshots ( opts , old_snapshots )
89+ |> fetch_operations ( opts )
90+ |> Enum . flat_map ( & elem ( & 1 , 1 ) )
91+ |> Enum . uniq ( )
92+ |> organize_operations ( )
93+ end
94+
95+ defp opts ( opts ) do
96+ case struct ( __MODULE__ , opts ) do
97+ % { check_generated: true } = opts ->
98+ % { opts | dry_run: true }
99+
100+ opts ->
101+ opts
102+ end
103+ end
104+
60105defp create_extension_migrations ( repos , opts ) do
61106for repo <- repos do
62107snapshot_file = Path . join ( opts . snapshot_path , "extensions.json" )
@@ -165,16 +210,23 @@ defmodule AshPostgres.MigrationGenerator do
165210if opts . check_generated , do: exit ( { :shutdown , 1 } )
166211
167212operations
168- |> sort_operations ( )
169- |> streamline ( )
170- |> group_into_phases ( )
171- |> comment_out_phases ( )
213+ |> organize_operations
172214|> build_up_and_down ( )
173215|> write_migration! ( snapshots , repo , opts , tenant? )
174216end
175217end )
176218end
177219
220+ defp organize_operations ( [ ] ) , do: [ ]
221+
222+ defp organize_operations ( operations ) do
223+ operations
224+ |> sort_operations ( )
225+ |> streamline ( )
226+ |> group_into_phases ( )
227+ |> comment_out_phases ( )
228+ end
229+
178230defp comment_out_phases ( phases ) do
179231Enum . map ( phases , fn
180232% { operations: operations } = phase ->
@@ -189,14 +241,20 @@ defmodule AshPostgres.MigrationGenerator do
189241end )
190242end
191243
192- defp deduplicate_snapshots ( snapshots , opts ) do
244+ defp deduplicate_snapshots ( snapshots , opts , existing_snapshots \\ [ ] ) do
193245snapshots
194246|> Enum . group_by ( fn snapshot ->
195247snapshot . table
196248end )
197249|> Enum . map ( fn { _table , [ snapshot | _ ] = snapshots } ->
198- existing_snapshot = get_existing_snapshot ( snapshot , opts )
199- { primary_key , identities } = merge_primary_keys ( existing_snapshot , snapshots )
250+ existing_snapshot =
251+ if opts . no_shell? do
252+ Enum . find ( existing_snapshots , & ( & 1 . table == snapshot . table ) )
253+ else
254+ get_existing_snapshot ( snapshot , opts )
255+ end
256+
257+ { primary_key , identities } = merge_primary_keys ( existing_snapshot , snapshots , opts )
200258
201259attributes = Enum . flat_map ( snapshots , & & 1 . attributes )
202260
@@ -336,7 +394,7 @@ defmodule AshPostgres.MigrationGenerator do
336394end
337395end
338396
339- defp merge_primary_keys ( nil , [ snapshot | _ ] = snapshots ) do
397+ defp merge_primary_keys ( nil , [ snapshot | _ ] = snapshots , opts ) do
340398snapshots
341399|> Enum . map ( & pkey_names ( & 1 . attributes ) )
342400|> Enum . uniq ( )
@@ -352,16 +410,20 @@ defmodule AshPostgres.MigrationGenerator do
352410"#{ index } :#{ inspect ( pkey ) } "
353411end )
354412
355- message = """
356- Which primary key should be used for the table `#{ snapshot . table } ` (enter the number)?
413+ choice =
414+ if opts . no_shell? do
415+ raise "Unimplemented: cannot resolve primary key ambiguity without shell input"
416+ else
417+ message = """
418+ Which primary key should be used for the table `#{ snapshot . table } ` (enter the number)?
357419
358- #{ unique_primary_key_names }
359- """
420+ #{ unique_primary_key_names }
421+ """
360422
361- choice =
362- message
363- |> Mix . shell ( ) . prompt ( )
364- |> String . to_integer ( )
423+ message
424+ |> Mix . shell ( ) . prompt ( )
425+ |> String . to_integer ( )
426+ end
365427
366428identities =
367429unique_primary_keys
@@ -387,7 +449,7 @@ defmodule AshPostgres.MigrationGenerator do
387449end
388450end
389451
390- defp merge_primary_keys ( existing_snapshot , snapshots ) do
452+ defp merge_primary_keys ( existing_snapshot , snapshots , opts ) do
391453pkey_names = pkey_names ( existing_snapshot . attributes )
392454
393455one_pkey_exists? =
@@ -413,7 +475,7 @@ defmodule AshPostgres.MigrationGenerator do
413475
414476{ pkey_names , identities }
415477else
416- merge_primary_keys ( nil , snapshots )
478+ merge_primary_keys ( nil , snapshots , opts )
417479end
418480end
419481
@@ -565,7 +627,8 @@ defmodule AshPostgres.MigrationGenerator do
565627end
566628end
567629
568- defp build_up_and_down ( phases ) do
630+ @ doc false
631+ def build_up_and_down ( phases ) do
569632up =
570633Enum . map_join ( phases , "\n " , fn phase ->
571634phase
@@ -924,7 +987,7 @@ defmodule AshPostgres.MigrationGenerator do
924987multitenancy: % {
925988attribute: nil ,
926989strategy: nil ,
927- global: false
990+ global: nil
928991}
929992}
930993
@@ -994,7 +1057,7 @@ defmodule AshPostgres.MigrationGenerator do
9941057end )
9951058
9961059{ attributes_to_add , attributes_to_remove , attributes_to_rename } =
997- resolve_renames ( snapshot . table , attributes_to_add , attributes_to_remove )
1060+ resolve_renames ( snapshot . table , attributes_to_add , attributes_to_remove , opts )
9981061
9991062attributes_to_alter =
10001063snapshot . attributes
@@ -1130,7 +1193,7 @@ defmodule AshPostgres.MigrationGenerator do
11301193
11311194snapshot_file
11321195|> File . read! ( )
1133- |> load_snapshot ( snapshot . table )
1196+ |> load_snapshot ( )
11341197end
11351198else
11361199get_old_snapshot ( folder , snapshot )
@@ -1145,37 +1208,60 @@ defmodule AshPostgres.MigrationGenerator do
11451208if File . exists? ( old_snapshot_file ) do
11461209old_snapshot_file
11471210|> File . read! ( )
1148- |> load_snapshot ( snapshot . table )
1211+ |> load_snapshot ( )
11491212end
11501213end
11511214
1152- defp resolve_renames ( _table , adding , [ ] ) , do: { adding , [ ] , [ ] }
1215+ defp resolve_renames ( _table , adding , [ ] , _opts ) , do: { adding , [ ] , [ ] }
11531216
1154- defp resolve_renames ( _table , [ ] , removing ) , do: { [ ] , removing , [ ] }
1217+ defp resolve_renames ( _table , [ ] , removing , _opts ) , do: { [ ] , removing , [ ] }
11551218
1156- defp resolve_renames ( table , [ adding ] , [ removing ] ) do
1157- if Mix . shell ( ) . yes? ( "Are you renaming #{ table } . #{ removing . name } to #{ table } . #{ adding . name } ?" ) do
1219+ defp resolve_renames ( table , [ adding ] , [ removing ] , opts ) do
1220+ if renaming_to? ( table , removing . name , adding . name , opts ) do
11581221{ [ ] , [ ] , [ { adding , removing } ] }
11591222else
11601223{ [ adding ] , [ removing ] , [ ] }
11611224end
11621225end
11631226
1164- defp resolve_renames ( table , adding , [ removing | rest ] ) do
1227+ defp resolve_renames ( table , adding , [ removing | rest ] , opts ) do
11651228{ new_adding , new_removing , new_renames } =
1166- if Mix . shell ( ) . yes? ( "Are you renaming#{ table } .#{ removing . name } ?" ) do
1167- new_attribute = get_new_attribute ( adding )
1229+ if renaming? ( table , removing , opts ) do
1230+ new_attribute =
1231+ if opts . no_shell? do
1232+ raise "Unimplemented: Cannot get new_attribute without the shell!"
1233+ else
1234+ get_new_attribute ( adding )
1235+ end
11681236
11691237{ adding -- [ new_attribute ] , [ ] , [ { new_attribute , removing } ] }
11701238else
11711239{ adding , [ removing ] , [ ] }
11721240end
11731241
1174- { rest_adding , rest_removing , rest_renames } = resolve_renames ( table , new_adding , rest )
1242+ { rest_adding , rest_removing , rest_renames } = resolve_renames ( table , new_adding , rest , opts )
11751243
11761244{ new_adding ++ rest_adding , new_removing ++ rest_removing , rest_renames ++ new_renames }
11771245end
11781246
1247+ defp renaming_to? ( table , removing , adding , opts ) do
1248+ if opts . no_shell? do
1249+ raise "Unimplemented: cannot determine: Are you renaming#{ table } .#{ removing } to#{ table } .#{
1250+ adding
1251+ } ? without shell input"
1252+ else
1253+ Mix . shell ( ) . yes? ( "Are you renaming#{ table } .#{ removing } to#{ table } .#{ adding } ?" )
1254+ end
1255+ end
1256+
1257+ defp renaming? ( table , removing , opts ) do
1258+ if opts . no_shell? do
1259+ raise "Unimplemented: cannot determine: Are you renaming#{ table } .#{ removing . name } ? without shell input"
1260+ else
1261+ Mix . shell ( ) . yes? ( "Are you renaming#{ table } .#{ removing . name } ?" )
1262+ end
1263+ end
1264+
11791265defp get_new_attribute ( adding , tries \\ 3 )
11801266
11811267defp get_new_attribute ( _adding , 0 ) do
@@ -1421,28 +1507,33 @@ defmodule AshPostgres.MigrationGenerator do
14211507end
14221508
14231509defp sanitize_type ( { :array , type } ) do
1424- [ "array" , type ]
1510+ [ "array" , sanitize_type ( type ) ]
14251511end
14261512
14271513defp sanitize_type ( type ) do
14281514type
14291515end
14301516
1431- defp load_snapshot ( json , table ) do
1517+ defp load_snapshot ( json ) do
14321518json
14331519|> Jason . decode! ( keys: :atoms! )
1520+ |> sanitize_snapshot ( )
1521+ end
1522+
1523+ defp sanitize_snapshot ( snapshot ) do
1524+ snapshot
14341525|> Map . put_new ( :has_create_action , true )
14351526|> Map . update! ( :identities , fn identities ->
14361527Enum . map ( identities , & load_identity / 1 )
14371528end )
14381529|> Map . update! ( :attributes , fn attributes ->
1439- Enum . map ( attributes , & load_attribute ( & 1 , table ) )
1530+ Enum . map ( attributes , & load_attribute ( & 1 , snapshot . table ) )
14401531end )
14411532|> Map . update! ( :repo , & String . to_atom / 1 )
14421533|> Map . put_new ( :multitenancy , % {
14431534attribute: nil ,
14441535strategy: nil ,
1445- global: false
1536+ global: nil
14461537} )
14471538|> Map . update! ( :multitenancy , & load_multitenancy / 1 )
14481539end
@@ -1472,7 +1563,7 @@ defmodule AshPostgres.MigrationGenerator do
14721563|> Map . put_new ( :multitenancy , % {
14731564attribute: nil ,
14741565strategy: nil ,
1475- global: false
1566+ global: nil
14761567} )
14771568|> Map . update! ( :multitenancy , & load_multitenancy / 1 )
14781569end )