Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .tool-versions
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
erlang 27.1.2
elixir 1.18.4-otp-27
elixir 1.18.1
pipx 1.8.0
27 changes: 2 additions & 25 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,34 +11,11 @@ See [Conventional Commits](https://www.conventionalcommits.org) for commit guide

<!-- changelog -->

## [v2.6.32](https://github.com/ash-project/ash_postgres/compare/v2.6.31...v2.6.32) (2026-02-11)



## [Unreleased]

### Bug Fixes:

* produce correct error on `restrict` managed_relationship behavior (#690) by lincolnhuls

### Improvements:

* Split up usage rules into sub-rules (#691) by Mylan Connolly

## [v2.6.31](https://github.com/ash-project/ash_postgres/compare/v2.6.30...v2.6.31) (2026-02-05)




### Bug Fixes:

* handle atomic no_rollback errors during creates by Zach Daniel

* correct test expectation for `first` aggregate with `include_nil?` (#683) by sevenseacat

## [v2.6.30](https://github.com/ash-project/ash_postgres/compare/v2.6.29...v2.6.30) (2026-02-04)


* fix for atomic create support
* avoid dropping and re-adding foreign key when only reference index changes (#611)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changelog is automatically managed. Please remove this and I'll merge 🥳


## [v2.6.29](https://github.com/ash-project/ash_postgres/compare/v2.6.28...v2.6.29) (2026-02-03)

Expand Down
23 changes: 21 additions & 2 deletions lib/migration_generator/migration_generator.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2153,7 +2153,8 @@ defmodule AshPostgres.MigrationGenerator do
table: snapshot.table,
schema: snapshot.schema,
source: attribute.source,
multitenancy: snapshot.multitenancy
multitenancy: snapshot.multitenancy,
old_multitenancy: old_snapshot.multitenancy
}
end)

Expand Down Expand Up @@ -2662,7 +2663,8 @@ defmodule AshPostgres.MigrationGenerator do
[]
end

if Map.get(old_attribute, :references) != Map.get(new_attribute, :references) do
if Map.get(old_attribute, :references) != Map.get(new_attribute, :references) and
references_differ_beyond_index?(old_attribute, new_attribute) do
redo_deferrability =
if has_reference?(old_snapshot.multitenancy, old_attribute) and
differently_deferrable?(new_attribute, old_attribute) do
Expand Down Expand Up @@ -2770,6 +2772,23 @@ defmodule AshPostgres.MigrationGenerator do

defp differently_deferrable?(_, _), do: false

# When the only reference change is index? (add/remove index), we should not emit
# DropForeignKey + AlterAttribute; the separate AddReferenceIndex/RemoveReferenceIndex
# operations handle it. This avoids migrations that drop and re-add the same FK.
defp references_differ_beyond_index?(old_attr, new_attr) do
old_refs = Map.get(old_attr, :references)
new_refs = Map.get(new_attr, :references)

cond do
old_refs == new_refs -> false
is_nil(old_refs) or is_nil(new_refs) -> true
true ->
old_without_index = Map.delete(old_refs, :index?)
new_without_index = Map.delete(new_refs, :index?)
old_without_index != new_without_index
end
end

# This exists to handle the fact that the remapping of the key name -> source caused attributes
# to be considered unequal. We ignore things that only differ in that way using this function.
defp attributes_unequal?(left, right, repo, _old_snapshot, _new_snapshot, ignore_names?) do
Expand Down
121 changes: 121 additions & 0 deletions test/migration_generator_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -1775,6 +1775,127 @@ defmodule AshPostgres.MigrationGeneratorTest do
assert File.read!(file) =~ ~S{create index(:posts, [:post_id])}
end

test "changing only reference index? does not drop and re-add foreign key (issue #611)" do
# First generate: reference with index?: true
defresource PostRefIdx, "posts" do
attributes do
uuid_primary_key(:id)
attribute(:key_id, :uuid, allow_nil?: false, public?: true)
attribute(:foobar, :string, public?: true)
end

actions do
defaults([:create, :read, :update, :destroy])
end
end

defresource Post2RefIdx, "posts" do
attributes do
uuid_primary_key(:id)
attribute(:name, :string, public?: true)
attribute(:related_key_id, :uuid, public?: true)
end

relationships do
belongs_to(:post, PostRefIdx, public?: true)
end

actions do
defaults([:create, :read, :update, :destroy])
end

postgres do
references do
reference(:post, index?: true)
end
end
end

defmodule DomainRefIdx do
use Ash.Domain
resources do
resource(PostRefIdx)
resource(Post2RefIdx)
end
end

AshPostgres.MigrationGenerator.generate(DomainRefIdx,
snapshot_path: "test_snapshots_path",
migration_path: "test_migration_path",
quiet: true,
format: false,
auto_name: true
)

# Second generate: same reference but index?: false (only index change)
defresource PostRefNoIdx, "posts" do
attributes do
uuid_primary_key(:id)
attribute(:key_id, :uuid, allow_nil?: false, public?: true)
attribute(:foobar, :string, public?: true)
end

actions do
defaults([:create, :read, :update, :destroy])
end
end

defresource Post2RefNoIdx, "posts" do
attributes do
uuid_primary_key(:id)
attribute(:name, :string, public?: true)
attribute(:related_key_id, :uuid, public?: true)
end

relationships do
belongs_to(:post, PostRefNoIdx, public?: true)
end

actions do
defaults([:create, :read, :update, :destroy])
end

postgres do
references do
reference(:post, index?: false)
end
end
end

defmodule DomainRefNoIdx do
use Ash.Domain
resources do
resource(PostRefNoIdx)
resource(Post2RefNoIdx)
end
end

AshPostgres.MigrationGenerator.generate(DomainRefNoIdx,
snapshot_path: "test_snapshots_path",
migration_path: "test_migration_path",
quiet: true,
format: false,
auto_name: true
)

[_, file2] =
Path.wildcard("test_migration_path/**/*_migrate_resources*.exs")
|> Enum.reject(&String.contains?(&1, "extensions"))
|> Enum.sort()

content = File.read!(file2)

# Should only drop the index, not touch the foreign key
assert content =~ ~S{drop_if_exists index(:posts, [:post_id])},
"migration should drop the reference index when index? changes to false"

refute content =~ ~S{drop constraint(:posts, "posts_post_id_fkey")},
"migration should not drop the foreign key when only index? changed (issue #611)"

refute content =~ ~S{modify :post_id, references(},
"migration should not modify references when only index? changed (issue #611)"
end

test "references with deferrable modifications generate changes with the correct schema" do
defposts do
attributes do
Expand Down