@@ -490,6 +490,106 @@ defmodule AshPostgres.MigrationGeneratorTest do
490490 end
491491 end
492492
493+ describe "unique identities with `concurrent_indexes: true`" do
494+ test "dependent foreign keys are generated only after the unique index migration" , % {
495+ snapshot_path: snapshot_path ,
496+ migration_path: migration_path
497+ } do
498+ Code . compiler_options ( ignore_module_conflict: true )
499+
500+ defmodule ConcurrentUniqueTarget do
501+ use Ash.Resource , data_layer: AshPostgres.DataLayer , domain: nil
502+
503+ postgres do
504+ table "concurrent_unique_targets"
505+ repo ( AshPostgres.TestRepo )
506+ end
507+
508+ attributes do
509+ uuid_primary_key ( :id )
510+ attribute ( :code , :string , allow_nil?: false , public?: true )
511+ end
512+
513+ identities do
514+ identity ( :uniq_code , [ :code ] )
515+ end
516+
517+ actions do
518+ defaults ( [ :create , :read , :update , :destroy ] )
519+ end
520+ end
521+
522+ defmodule ConcurrentUniqueDependent do
523+ use Ash.Resource , data_layer: AshPostgres.DataLayer , domain: nil
524+
525+ postgres do
526+ table "concurrent_unique_dependents"
527+ repo ( AshPostgres.TestRepo )
528+ end
529+
530+ attributes do
531+ uuid_primary_key ( :id )
532+ attribute ( :target_code , :string , public?: true )
533+ end
534+
535+ relationships do
536+ belongs_to ( :target , ConcurrentUniqueTarget ) do
537+ source_attribute ( :target_code )
538+ destination_attribute ( :code )
539+ attribute_writable? ( true )
540+ allow_nil? ( true )
541+ public? ( true )
542+ end
543+ end
544+
545+ actions do
546+ defaults ( [ :create , :read , :update , :destroy ] )
547+ end
548+ end
549+
550+ defmodule ConcurrentUniqueDomain do
551+ use Ash.Domain
552+
553+ resources do
554+ resource ( ConcurrentUniqueTarget )
555+ resource ( ConcurrentUniqueDependent )
556+ end
557+ end
558+
559+ Code . compiler_options ( ignore_module_conflict: false )
560+
561+ AshPostgres.MigrationGenerator . generate ( ConcurrentUniqueDomain ,
562+ snapshot_path: snapshot_path ,
563+ migration_path: migration_path ,
564+ quiet: true ,
565+ format: false ,
566+ auto_name: true ,
567+ concurrent_indexes: true
568+ )
569+
570+ assert [ migration_before_index , unique_index_migration ] =
571+ Enum . sort ( Path . wildcard ( "#{ migration_path } /**/*_migrate_resources*.exs" ) )
572+ |> Enum . reject ( & String . contains? ( & 1 , "extensions" ) )
573+
574+ first_contents = File . read! ( migration_before_index )
575+ second_contents = File . read! ( unique_index_migration )
576+
577+ # The correct end state here likely needs three steps:
578+ # 1. create/alter tables without the FK to `:code`
579+ # 2. create the concurrent unique index on `concurrent_unique_targets.code`
580+ # 3. add the FK from `concurrent_unique_dependents.target_code`
581+ #
582+ # With only two files, the FK must not appear before the concurrent unique
583+ # index migration, because Postgres requires the referenced column to be
584+ # backed by a unique or primary key constraint before the FK is added.
585+ assert first_contents =~
586+ ~S| create unique_index(:concurrent_unique_targets, [:code], name: "concurrent_unique_targets_uniq_code_index", concurrently: true)|
587+
588+ assert second_contents =~
589+ ~S| modify :target_code, references(:concurrent_unique_targets, column: :code, name: "concurrent_unique_dependents_target_code_fkey", type: :text, prefix: "public")|
590+ end
591+ end
592+
493593 describe "custom_indexes with `null_distinct: false`" do
494594 setup % { snapshot_path: snapshot_path , migration_path: migration_path } do
495595 :ok
0 commit comments