Skip to content
Merged
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
33 changes: 22 additions & 11 deletions lib/data_layer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2310,7 +2310,7 @@ defmodule AshPostgres.DataLayer do
%Postgrex.Error{} = error,
stacktrace,
{:bulk_create, fake_changeset},
_resource
resource
) do
case Ecto.Adapters.Postgres.Connection.to_constraints(error, []) do
[] ->
Expand All @@ -2319,7 +2319,7 @@ defmodule AshPostgres.DataLayer do
constraints ->
{:error,
fake_changeset
|> constraints_to_errors(:insert, constraints)
|> constraints_to_errors(:insert, constraints, resource)
|> Ash.Error.to_ash_error()}
end
end
Expand Down Expand Up @@ -2372,7 +2372,7 @@ defmodule AshPostgres.DataLayer do
{:error, Ash.Error.to_ash_error(error, stacktrace)}
end

defp constraints_to_errors(%{constraints: user_constraints} = changeset, action, constraints) do
defp constraints_to_errors(%{constraints: user_constraints} = changeset, action, constraints, resource) do
Enum.map(constraints, fn {type, constraint} ->
user_constraint =
Enum.find(user_constraints, fn c ->
Expand All @@ -2387,14 +2387,25 @@ defmodule AshPostgres.DataLayer do

case user_constraint do
%{field: field, error_message: error_message, type: type, constraint: constraint} ->
Ash.Error.Changes.InvalidAttribute.exception(
field: field,
message: error_message,
private_vars: [
constraint: constraint,
constraint_type: type
]
)
identities = Ash.Resource.Info.identities(resource)
table = AshPostgres.DataLayer.Info.table(resource)

identity = Enum.find(identities, fn identity ->
"#{table}_#{identity.name}_index" == constraint
end)

field_names = if identity, do: identity.field_names, else: [field]

Enum.map(field_names, fn field_name ->
Ash.Error.Changes.InvalidAttribute.exception(
field: field_name,
message: error_message,
private_vars: [
constraint: constraint,
constraint_type: type
]
)
end)

nil ->
Ecto.ConstraintError.exception(
Expand Down
4 changes: 2 additions & 2 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ defmodule AshPostgres.MixProject do
# Run "mix help deps" to learn about dependencies.
defp deps do
[
{:ash, ash_version("~> 3.4 and >= 3.4.48")},
{:ash, ash_version("~> 3.4 and >= 3.4.64")},
{:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.43")},
{:igniter, "~> 0.5 and >= 0.5.16", optional: true},
{:ecto_sql, "~> 3.12"},
Expand Down Expand Up @@ -197,7 +197,7 @@ defmodule AshPostgres.MixProject do
[path: "../ash", override: true]

"main" ->
[git: "https://github.com/ash-project/ash.git"]
[git: "https://github.com/ash-project/ash.git", override: true]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

If I don't add override, then I'm not able to use main option for ASH_VERSION variable.

Copy link
Contributor

Choose a reason for hiding this comment

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

Interesting, I haven't had that problem but adding that should fine.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Without override: true, I was getting a following error:

Dependencies have diverged:
* ash (https://github.com/ash-project/ash.git)
  the dependency ash in mix.exs is overriding a child dependency:

  > In mix.exs:
    {:ash, [env: :prod, git: "https://github.com/ash-project/ash.git"]}

  > In deps/ash_sql/mix.exs:
    {:ash, "~> 3.0 and >= 3.4.60", [env: :prod, hex: "ash", repo: "hexpm"]}

  Ensure they match or specify one of the above in your deps and set "override: true"
** (Mix) Can't continue due to errors on dependencies


version when is_binary(version) ->
"~> #{version}"
Expand Down
64 changes: 64 additions & 0 deletions priv/resource_snapshots/test_repo/orgs/20250210191116.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "fragment(\"gen_random_uuid()\")",
"generated?": false,
"primary_key?": true,
"references": null,
"size": null,
"source": "id",
"type": "uuid"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "name",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "department",
"type": "text"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "1D1BA9E1E272238D80C9861CAA67C4A85F675E3B052A15F4D5AC272551B820A7",
"identities": [
{
"all_tenants?": false,
"base_filter": null,
"index_name": "orgs_department_index",
"keys": [
{
"type": "string",
"value": "(LOWER(department))"
}
],
"name": "department",
"nils_distinct?": true,
"where": null
}
],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.AshPostgres.TestRepo",
"schema": null,
"table": "orgs"
}
25 changes: 25 additions & 0 deletions priv/test_repo/migrations/20250210191116_migrate_resources49.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
defmodule AshPostgres.TestRepo.Migrations.MigrateResources49 do
@moduledoc """
Updates resources based on their most recent snapshots.

This file was autogenerated with `mix ash_postgres.generate_migrations`
"""

use Ecto.Migration

def up do
alter table(:orgs) do
add(:department, :text)
end

create(unique_index(:orgs, ["(LOWER(department))"], name: "orgs_department_index"))
end

def down do
drop_if_exists(unique_index(:orgs, ["(LOWER(department))"], name: "orgs_department_index"))

alter table(:orgs) do
remove(:department)
end
end
end
11 changes: 11 additions & 0 deletions test/support/resources/organization.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ defmodule AshPostgres.Test.Organization do
postgres do
table("orgs")
repo(AshPostgres.TestRepo)

calculations_to_sql(lower_department: "LOWER(department)")
end

policies do
Expand Down Expand Up @@ -39,6 +41,15 @@ defmodule AshPostgres.Test.Organization do
attributes do
uuid_primary_key(:id, writable?: true)
attribute(:name, :string, public?: true)
attribute(:department, :string, public?: true)
end

calculations do
calculate(:lower_department, :string, expr(fragment("LOWER(?)", department)))
end

identities do
identity(:department, [:lower_department], field_names: [:department_slug])
end

relationships do
Expand Down
14 changes: 14 additions & 0 deletions test/unique_identity_test.exs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
defmodule AshPostgres.Test.UniqueIdentityTest do
use AshPostgres.RepoCase, async: false
alias AshPostgres.Test.Post
alias AshPostgres.Test.Organization

require Ash.Query

Expand All @@ -19,6 +20,19 @@ defmodule AshPostgres.Test.UniqueIdentityTest do
end
end

test "unique constraint field names are property set" do
Organization
|> Ash.Changeset.for_create(:create, %{name: "Acme", department: "Sales"})
|> Ash.create!()

assert {:error, %Ash.Error.Invalid{errors: [invalid_attribute]}} =
Organization
|> Ash.Changeset.for_create(:create, %{name: "Acme", department: "SALES"})
|> Ash.create()

assert %Ash.Error.Changes.InvalidAttribute{field: :department_slug} = invalid_attribute
end

test "a unique constraint can be used to upsert when the resource has a base filter" do
post =
Post
Expand Down